Audacity  2.2.2
Menus.cpp
Go to the documentation of this file.
1 /**********************************************************************
2 
3  Audacity: A Digital Audio Editor
4 
5  Menus.cpp
6 
7  Dominic Mazzoni
8  Brian Gunlogson
9  et al.
10 
11 *******************************************************************//****************************************************************//*******************************************************************/
32 
33 #include "Audacity.h"
34 #include "Project.h"
35 
36 #include <cfloat>
37 #include <iterator>
38 #include <algorithm>
39 #include <limits>
40 #include <math.h>
41 
42 
43 #include <wx/defs.h>
44 #include <wx/docview.h>
45 #include <wx/filedlg.h>
46 #include <wx/textfile.h>
47 #include <wx/textdlg.h>
48 #include <wx/progdlg.h>
49 #include <wx/scrolbar.h>
50 #include <wx/ffile.h>
51 #include <wx/statusbr.h>
52 #include <wx/utils.h>
53 
54 #include "FreqWindow.h"
55 #include "effects/Contrast.h"
56 #include "TrackPanel.h"
57 
58 #include "effects/EffectManager.h"
59 
60 #include "AudacityApp.h"
61 #include "AudacityLogger.h"
62 #include "AudioIO.h"
63 #include "Dependencies.h"
64 #include "float_cast.h"
65 #include "LabelTrack.h"
66 #ifdef USE_MIDI
67 #include "import/ImportMIDI.h"
68 #endif // USE_MIDI
69 #include "import/ImportRaw.h"
70 #include "export/Export.h"
71 #include "export/ExportMultiple.h"
72 #include "prefs/PrefsDialog.h"
73 #include "prefs/PlaybackPrefs.h"
74 #include "ShuttleGui.h"
75 #include "HistoryWindow.h"
76 #include "LyricsWindow.h"
77 #include "MixerBoard.h"
78 #include "Internat.h"
79 #include "FileFormats.h"
80 #include "ModuleManager.h"
81 #include "PluginManager.h"
82 #include "Prefs.h"
83 #include "Printing.h"
84 #ifdef USE_MIDI
85 #include "NoteTrack.h"
86 #endif // USE_MIDI
87 #include "Tags.h"
88 #include "TimeTrack.h"
89 #include "Mix.h"
90 #include "AboutDialog.h"
91 #include "Benchmark.h"
92 #include "Screenshot.h"
93 #include "ondemand/ODManager.h"
94 
95 #include "BatchProcessDialog.h"
96 #include "BatchCommands.h"
97 #include "prefs/BatchPrefs.h"
98 
99 #include "toolbars/ToolManager.h"
100 #include "toolbars/ControlToolBar.h"
101 #include "toolbars/ToolsToolBar.h"
102 #include "toolbars/EditToolBar.h"
103 #include "toolbars/DeviceToolBar.h"
104 #include "toolbars/MixerToolBar.h"
106 
107 #include "tracks/ui/SelectHandle.h"
108 
110 
111 #include "Experimental.h"
112 #include "PlatformCompatibility.h"
113 #include "FileNames.h"
114 #include "TimeDialog.h"
115 #include "TimerRecordDialog.h"
116 #include "SoundActivatedRecord.h"
117 #include "LabelDialog.h"
118 
119 #include "SplashDialog.h"
120 #include "widgets/HelpSystem.h"
121 #include "DeviceManager.h"
122 
123 #include "UndoManager.h"
124 #include "WaveTrack.h"
125 
126 #if defined(EXPERIMENTAL_CRASH_REPORT)
127 #include <wx/debugrpt.h>
128 #endif
129 
130 #ifdef EXPERIMENTAL_SCOREALIGN
132 #include "audioreader.h"
133 #include "scorealign.h"
134 #include "scorealign-glue.h"
135 #endif /* EXPERIMENTAL_SCOREALIGN */
136 
137 #include "tracks/ui/Scrubbing.h"
138 #include "prefs/TracksPrefs.h"
139 
140 #include "widgets/Meter.h"
141 #include "widgets/ErrorDialog.h"
143 #include "commands/CommandContext.h"
144 
145 enum {
151  // The next two are only in one subMenu, so more easily handled at the end.
154 };
155 
156 // Post Timer Recording Actions
157 // Ensure this matches the enum in TimerRecordDialog.cpp
158 enum {
166 };
167 
168 #include "commands/CommandContext.h"
170 
171 #include "BatchCommands.h"
172 
173 
174 //
175 // Effects menu arrays
176 //
177 static bool SortEffectsByName(const PluginDescriptor *a, const PluginDescriptor *b)
178 {
179  auto akey = a->GetSymbol().Translation();
180  auto bkey = b->GetSymbol().Translation();
181 
182  akey += a->GetPath();
183  bkey += b->GetPath();
184 
185  return akey.CmpNoCase(bkey) < 0;
186 }
187 
189 {
190  auto &em = EffectManager::Get();
191  auto akey = em.GetVendorName(a->GetID());
192  auto bkey = em.GetVendorName(b->GetID());
193 
194  if (akey.IsEmpty())
195  {
196  akey = _("Uncategorized");
197  }
198  if (bkey.IsEmpty())
199  {
200  bkey = _("Uncategorized");
201  }
202 
203  akey += a->GetSymbol().Translation();
204  bkey += b->GetSymbol().Translation();
205 
206  akey += a->GetPath();
207  bkey += b->GetPath();
208 
209  return akey.CmpNoCase(bkey) < 0;
210 }
211 
213 {
214  auto &em = EffectManager::Get();
215  auto akey = em.GetVendorName(a->GetID());
216  auto bkey = em.GetVendorName(b->GetID());
217 
218  if (a->IsEffectDefault())
219  {
220  akey = wxEmptyString;
221  }
222  if (b->IsEffectDefault())
223  {
224  bkey = wxEmptyString;
225  }
226 
227  akey += a->GetSymbol().Translation();
228  bkey += b->GetSymbol().Translation();
229 
230  akey += a->GetPath();
231  bkey += b->GetPath();
232 
233  return akey.CmpNoCase(bkey) < 0;
234 }
235 
237 {
238  auto &em = EffectManager::Get();
239  auto akey = em.GetEffectFamilyName(a->GetID());
240  auto bkey = em.GetEffectFamilyName(b->GetID());
241 
242  if (akey.IsEmpty())
243  {
244  akey = _("Uncategorized");
245  }
246  if (bkey.IsEmpty())
247  {
248  bkey = _("Uncategorized");
249  }
250 
251  if (a->IsEffectDefault())
252  {
253  akey = wxEmptyString;
254  }
255  if (b->IsEffectDefault())
256  {
257  bkey = wxEmptyString;
258  }
259 
260  akey += a->GetSymbol().Translation();
261  bkey += b->GetSymbol().Translation();
262 
263  akey += a->GetPath();
264  bkey += b->GetPath();
265 
266  return akey.CmpNoCase(bkey) < 0;
267 }
268 
269 static bool SortEffectsByType(const PluginDescriptor *a, const PluginDescriptor *b)
270 {
271  auto &em = EffectManager::Get();
272  auto akey = em.GetEffectFamilyName(a->GetID());
273  auto bkey = em.GetEffectFamilyName(b->GetID());
274 
275  if (akey.IsEmpty())
276  {
277  akey = _("Uncategorized");
278  }
279  if (bkey.IsEmpty())
280  {
281  bkey = _("Uncategorized");
282  }
283 
284  akey += a->GetSymbol().Translation();
285  bkey += b->GetSymbol().Translation();
286 
287  akey += a->GetPath();
288  bkey += b->GetPath();
289 
290  return akey.CmpNoCase(bkey) < 0;
291 }
292 
296 
297 // To supply the "finder" argument in AddItem calls
298 static CommandHandlerObject &ident(AudacityProject &project) { return project; }
299 
300 #define FN(X) ident, static_cast<CommandFunctorPointer>(& AudacityProject :: X)
301 #define XXO(X) _(X), wxString{X}.Contains("...")
302 
304 {
306  wxArrayString names;
307  std::vector<int> indices;
308 
309  // The list of defaults to exclude depends on
310  // preference wxT("/GUI/Shortcuts/FullDefaults"), which may have changed.
311  c->SetMaxList();
312 
313  {
314  auto menubar = c->AddMenuBar(wxT("appmenu"));
315  wxASSERT(menubar);
316  c->SetOccultCommands( false );
317 
319  // File menu
321 
322  c->BeginMenu(_("&File"));
324 
325  /*i18n-hint: "New" is an action (verb) to create a NEW project*/
326  c->AddItem(wxT("New"), XXO("&New"), FN(OnNew), wxT("Ctrl+N"),
329 
330  /*i18n-hint: (verb)*/
331  c->AddItem(wxT("Open"), XXO("&Open..."), FN(OnOpen), wxT("Ctrl+O"),
334 
336 
338 
340 
341  c->AddItem(wxT("Close"), XXO("&Close"), FN(OnClose), wxT("Ctrl+W"));
342 
343  c->AddSeparator();
344 
345  c->BeginSubMenu( _("&Save Project") );
346  c->AddItem(wxT("Save"), XXO("&Save Project"), FN(OnSave), wxT("Ctrl+S"),
349  c->AddItem(wxT("SaveAs"), XXO("Save Project &As..."), FN(OnSaveAs));
350  // TODO: The next two items should be disabled if project is empty
351  c->AddItem(wxT("SaveCopy"), XXO("Save Lossless Copy of Project..."), FN(OnSaveCopy));
352 #ifdef USE_LIBVORBIS
353  c->AddItem(wxT("SaveCompressed"), XXO("&Save Compressed Copy of Project..."), FN(OnSaveCompressed));
354 #endif
355  c->EndSubMenu();
356  c->AddSeparator();
357 
358  c->BeginSubMenu( _("&Export") );
359 
360  // Enable Export audio commands only when there are audio tracks.
361  c->AddItem(wxT("ExportMp3"), XXO("Export as MP&3"), FN(OnExportMp3), wxT(""),
364 
365  c->AddItem(wxT("ExportWav"), XXO("Export as &WAV"), FN(OnExportWav), wxT(""),
368 
369  c->AddItem(wxT("ExportOgg"), XXO("Export as &OGG"), FN(OnExportOgg), wxT(""),
372 
373  c->AddItem(wxT("Export"), XXO("&Export Audio..."), FN(OnExportAudio), wxT("Ctrl+Shift+E"),
376 
377  // Enable Export Selection commands only when there's a selection.
378  c->AddItem(wxT("ExportSel"), XXO("Expo&rt Selected Audio..."), FN(OnExportSelection),
381 
382  c->AddItem(wxT("ExportLabels"), XXO("Export &Labels..."), FN(OnExportLabels),
385  // Enable Export audio commands only when there are audio tracks.
386  c->AddItem(wxT("ExportMultiple"), XXO("Export &Multiple..."), FN(OnExportMultiple), wxT("Ctrl+Shift+L"),
389 #if defined(USE_MIDI)
390  c->AddItem(wxT("ExportMIDI"), XXO("Export MI&DI..."), FN(OnExportMIDI),
393 #endif
394  c->EndSubMenu();
395 
396  c->BeginSubMenu(_("&Import"));
397 
398  c->AddItem(wxT("ImportAudio"), XXO("&Audio..."), FN(OnImport), wxT("Ctrl+Shift+I"));
399  c->AddItem(wxT("ImportLabels"), XXO("&Labels..."), FN(OnImportLabels));
400 #ifdef USE_MIDI
401  c->AddItem(wxT("ImportMIDI"), XXO("&MIDI..."), FN(OnImportMIDI));
402 #endif // USE_MIDI
403  c->AddItem(wxT("ImportRaw"), XXO("&Raw Data..."), FN(OnImportRaw));
404 
405  c->EndSubMenu();
406  c->AddSeparator();
407 
409 
410  c->AddItem(wxT("PageSetup"), XXO("Pa&ge Setup..."), FN(OnPageSetup),
413  /* i18n-hint: (verb) It's item on a menu. */
414  c->AddItem(wxT("Print"), XXO("&Print..."), FN(OnPrint),
417 
418  c->AddSeparator();
419 
420  // On the Mac, the Exit item doesn't actually go here...wxMac will pull it out
421  // and put it in the Audacity menu for us based on its ID.
422  /* i18n-hint: (verb) It's item on a menu. */
423  c->AddItem(wxT("Exit"), XXO("E&xit"), FN(OnExit), wxT("Ctrl+Q"),
426 
427  c->EndMenu();
428 
430  // Edit Menu
432 
433  c->BeginMenu(_("&Edit"));
434 
437 
438  c->AddItem(wxT("Undo"), XXO("&Undo"), FN(OnUndo), wxT("Ctrl+Z"),
441 
442  // The default shortcut key for Redo is different on different platforms.
443  wxString key =
444 #ifdef __WXMSW__
445  wxT("Ctrl+Y");
446 #else
447  wxT("Ctrl+Shift+Z");
448 #endif
449 
450  c->AddItem(wxT("Redo"), XXO("&Redo"), FN(OnRedo), key,
453 
455 
456  c->AddSeparator();
457 
458  // Basic Edit coomands
459  /* i18n-hint: (verb)*/
460  c->AddItem(wxT("Cut"), XXO("Cu&t"), FN(OnCut), wxT("Ctrl+X"),
463  c->AddItem(wxT("Delete"), XXO("&Delete"), FN(OnDelete), wxT("Ctrl+K"),
466  /* i18n-hint: (verb)*/
467  c->AddItem(wxT("Copy"), XXO("&Copy"), FN(OnCopy), wxT("Ctrl+C"),
470  /* i18n-hint: (verb)*/
471  c->AddItem(wxT("Paste"), XXO("&Paste"), FN(OnPaste), wxT("Ctrl+V"),
474  /* i18n-hint: (verb)*/
475  c->AddItem(wxT("Duplicate"), XXO("Duplic&ate"), FN(OnDuplicate), wxT("Ctrl+D"));
476 
477  c->AddSeparator();
478 
479  c->BeginSubMenu(_("R&emove Special"));
480  /* i18n-hint: (verb) Do a special kind of cut*/
481  c->AddItem(wxT("SplitCut"), XXO("Spl&it Cut"), FN(OnSplitCut), wxT("Ctrl+Alt+X"));
482  /* i18n-hint: (verb) Do a special kind of DELETE*/
483  c->AddItem(wxT("SplitDelete"), XXO("Split D&elete"), FN(OnSplitDelete), wxT("Ctrl+Alt+K"));
484 
485  c->AddSeparator();
486 
487  /* i18n-hint: (verb)*/
488  c->AddItem(wxT("Silence"), XXO("Silence Audi&o"), FN(OnSilence), wxT("Ctrl+L"),
491  /* i18n-hint: (verb)*/
492  c->AddItem(wxT("Trim"), XXO("Tri&m Audio"), FN(OnTrim), wxT("Ctrl+T"),
495  c->EndSubMenu();
496 
497  c->AddSeparator();
498 
500 
501  c->BeginSubMenu(_("Clip B&oundaries"));
502  /* i18n-hint: (verb) It's an item on a menu. */
503  c->AddItem(wxT("Split"), XXO("Sp&lit"), FN(OnSplit), wxT("Ctrl+I"),
506  c->AddItem(wxT("SplitNew"), XXO("Split Ne&w"), FN(OnSplitNew), wxT("Ctrl+Alt+I"),
509  c->AddSeparator();
510  /* i18n-hint: (verb)*/
511  c->AddItem(wxT("Join"), XXO("&Join"), FN(OnJoin), wxT("Ctrl+J"));
512  c->AddItem(wxT("Disjoin"), XXO("Detac&h at Silences"), FN(OnDisjoin), wxT("Ctrl+Alt+J"));
513  c->EndSubMenu();
514 
516 
517  c->BeginSubMenu(_("&Labels"));
518 
519  c->AddItem(wxT("EditLabels"), XXO("&Edit Labels..."), FN(OnEditLabels),
521 
522  c->AddSeparator();
523 
524  c->AddItem(wxT("AddLabel"), XXO("Add Label at &Selection"), FN(OnAddLabel), wxT("Ctrl+B"),
526  c->AddItem(wxT("AddLabelPlaying"), XXO("Add Label at &Playback Position"),
528 #ifdef __WXMAC__
529  wxT("Ctrl+."),
530 #else
531  wxT("Ctrl+M"),
532 #endif
534  AudioIOBusyFlag);
536  c->AddItem(wxT("PasteNewLabel"), XXO("Paste Te&xt to New Label"), FN(OnPasteNewLabel), wxT("Ctrl+Alt+V"),
538 
539  c->AddSeparator();
540 
541  c->AddCheck(wxT("TypeToCreateLabel"), XXO("&Type to Create a Label (on/off)"),
543 
544  c->EndSubMenu();
545 
547 
548  c->BeginSubMenu(_("La&beled Audio"));
549 
552 
553  /* i18n-hint: (verb)*/
554  c->SetLongName( _("Label Cut"))->AddItem(wxT("CutLabels"), XXO("&Cut"), FN(OnCutLabels), wxT("Alt+X"),
557  c->SetLongName( _("Label Delete"))->AddItem(wxT("DeleteLabels"), XXO("&Delete"), FN(OnDeleteLabels), wxT("Alt+K"),
560 
561  c->AddSeparator();
562 
563  /* i18n-hint: (verb) A special way to cut out a piece of audio*/
564  c->SetLongName( _("Label Split Cut"))->AddItem(wxT("SplitCutLabels"), XXO("&Split Cut"), FN(OnSplitCutLabels), wxT("Alt+Shift+X"));
565  c->SetLongName( _("Label Split Delete"))->AddItem(wxT("SplitDeleteLabels"), XXO("Sp&lit Delete"), FN(OnSplitDeleteLabels), wxT("Alt+Shift+K"));
566 
567  c->AddSeparator();
568 
569 
570  c->SetLongName( _("Label Silence"))->AddItem(wxT("SilenceLabels"), XXO("Silence &Audio"), FN(OnSilenceLabels), wxT("Alt+L"));
571  /* i18n-hint: (verb)*/
572  c->SetLongName( _("Label Copy"))->AddItem(wxT("CopyLabels"), XXO("Co&py"), FN(OnCopyLabels), wxT("Alt+Shift+C"));
573 
574  c->AddSeparator();
575 
576  /* i18n-hint: (verb)*/
577  c->SetLongName( _("Label Split"))->AddItem(wxT("SplitLabels"), XXO("Spli&t"), FN(OnSplitLabels), wxT("Alt+I"),
580  /* i18n-hint: (verb)*/
581  c->SetLongName( _("Label Join"))->AddItem(wxT("JoinLabels"), XXO("&Join"), FN(OnJoinLabels), wxT("Alt+J"));
582  c->AddItem(wxT("DisjoinLabels"), XXO("Detac&h at Silences"), FN(OnDisjoinLabels), wxT("Alt+Shift+J"));
583 
584  c->EndSubMenu();
585 
586  c->AddItem(wxT("EditMetaData"), XXO("Me&tadata..."), FN(OnEditMetadata),
588 
590 
591 #ifndef __WXMAC__
592  c->AddSeparator();
593 #endif
594 
595  // The default shortcut key for Preferences is different on different platforms.
596  key =
597 #ifdef __WXMAC__
598  wxT("Ctrl+,");
599 #else
600  wxT("Ctrl+P");
601 #endif
602 
603  c->AddItem(wxT("Preferences"), XXO("Pre&ferences..."), FN(OnPreferences), key,
606 
607  c->EndMenu();
608 
610  // Select Menu
612 
613  /* i18n-hint: (verb) It's an item on a menu. */
614  c->BeginMenu(_("&Select"));
616 
617  c->SetLongName( _("Select All"))->AddItem(wxT("SelectAll"), XXO("&All"), FN(OnSelectAll), wxT("Ctrl+A"));
618  c->SetLongName( _("Select None"))->AddItem(wxT("SelectNone"), XXO("&None"), FN(OnSelectNone), wxT("Ctrl+Shift+A"));
619 
621 
623 
624  c->BeginSubMenu(_("&Tracks"));
625  c->AddItem(wxT("SelAllTracks"), XXO("In All &Tracks"), FN(OnSelectAllTracks),
626  wxT("Ctrl+Shift+K"),
628 
629 #ifdef EXPERIMENTAL_SYNC_LOCK
630  c->SetLongName( _("Select Sync-Locked"))->AddItem(wxT("SelSyncLockTracks"), XXO("In All &Sync-Locked Tracks"),
631  FN(OnSelectSyncLockSel), wxT("Ctrl+Shift+Y"),
634 #endif
635 
636  c->EndSubMenu();
637 
639 
641 
642  c->BeginSubMenu(_("R&egion"));
643 
644  c->SetLongName( _("Set Selection Left at Play Position"))->AddItem(wxT("SetLeftSelection"), XXO("&Left at Playback Position"), FN(OnSetLeftSelection), wxT("["));
645  c->SetLongName( _("Set Selection Right at Play Position"))->AddItem(wxT("SetRightSelection"), XXO("&Right at Playback Position"), FN(OnSetRightSelection), wxT("]"));
647  c->SetLongName( _("Select Track Start to Cursor"))->AddItem(wxT("SelTrackStartToCursor"), XXO("Track &Start to Cursor"), FN(OnSelectStartCursor), wxT("Shift+J"),AlwaysEnabledFlag,AlwaysEnabledFlag);
648  c->SetLongName( _("Select Cursor to Track End"))->AddItem(wxT("SelCursorToTrackEnd"), XXO("Cursor to Track &End"), FN(OnSelectCursorEnd), wxT("Shift+K"),AlwaysEnabledFlag,AlwaysEnabledFlag);
649  c->SetLongName( _("Select Track Start to End"))->AddItem(wxT("SelTrackStartToEnd"), XXO("Track Start to En&d"), FN(OnSelectTrackStartToEnd), wxT(""),AlwaysEnabledFlag,AlwaysEnabledFlag);
650  c->AddSeparator();
651  // GA: Audacity had 'Store Re&gion' here previously. There is no one-step
652  // way to restore the 'Saved Cursor Position' in Select Menu, so arguably
653  // using the word 'Selection' to do duty for both saving the region or the
654  // cursor is better. But it does not belong in a 'Region' submenu.
655  c->AddItem(wxT("SelSave"), XXO("S&tore Selection"), FN(OnSelectionSave),
658  // Audacity had 'Retrieve Regio&n' here previously.
659  c->AddItem(wxT("SelRestore"), XXO("Retrieve Selectio&n"), FN(OnSelectionRestore),
662 
663  c->EndSubMenu();
664 
666 
668 
669 #ifdef EXPERIMENTAL_SPECTRAL_EDITING
670  c->BeginSubMenu(_("S&pectral"));
671  c->AddItem(wxT("ToggleSpectralSelection"), XXO("To&ggle Spectral Selection"), FN(OnToggleSpectralSelection), wxT("Q"));
672  c->AddItem(wxT("NextHigherPeakFrequency"), XXO("Next &Higher Peak Frequency"), FN(OnNextHigherPeakFrequency));
673  c->AddItem(wxT("NextLowerPeakFrequency"), XXO("Next &Lower Peak Frequency"), FN(OnNextLowerPeakFrequency));
674  c->EndSubMenu();
675 #endif
676 
678 
680 
681  c->BeginSubMenu(_("Clip B&oundaries"));
682  c->AddItem(wxT("SelPrevClipBoundaryToCursor"), XXO("Pre&vious Clip Boundary to Cursor"),
685  c->AddItem(wxT("SelCursorToNextClipBoundary"), XXO("Cursor to Ne&xt Clip Boundary"),
688  c->SetLongName( _("Select Previous Clip"))->AddItem(wxT("SelPrevClip"), XXO("Previo&us Clip"), FN(OnSelectPrevClip), wxT("Alt+,"),
690  c->SetLongName( _("Select Next Clip"))->AddItem(wxT("SelNextClip"), XXO("N&ext Clip"), FN(OnSelectNextClip), wxT("Alt+."),
692 
693  c->EndSubMenu();
695 
696  c->AddSeparator();
697 
698  c->SetLongName( _("Select Cursor to Stored"))->AddItem(wxT("SelCursorStoredCursor"), XXO("Cursor to Stored &Cursor Position"), FN(OnSelectCursorStoredCursor),
699  wxT(""), TracksExistFlag, TracksExistFlag);
700 
701  c->AddItem(wxT("StoreCursorPosition"), XXO("Store Cursor Pos&ition"), FN(OnCursorPositionStore),
704  // Save cursor position is used in some selections.
705  // Maybe there should be a restore for it?
706 
707  c->AddSeparator();
708 
709  c->SetLongName( _("Select Zero Crossing"))->AddItem(wxT("ZeroCross"), XXO("At &Zero Crossings"), FN(OnZeroCrossing), wxT("Z"));
710 
711  c->EndMenu();
712 
714  // View Menu
716 
717  c->BeginMenu(_("&View"));
719  c->BeginSubMenu(_("&Zoom"));
720 
721  c->AddItem(wxT("ZoomIn"), XXO("Zoom &In"), FN(OnZoomIn), wxT("Ctrl+1"),
724  c->AddItem(wxT("ZoomNormal"), XXO("Zoom &Normal"), FN(OnZoomNormal), wxT("Ctrl+2"));
725  c->AddItem(wxT("ZoomOut"), XXO("Zoom &Out"), FN(OnZoomOut), wxT("Ctrl+3"),
728  c->AddItem(wxT("ZoomSel"), XXO("&Zoom to Selection"), FN(OnZoomSel), wxT("Ctrl+E"),
731  c->AddItem(wxT("ZoomToggle"), XXO("Zoom &Toggle"), FN(OnZoomToggle), wxT("Shift+Z"),
734  c->EndSubMenu();
735 
736  c->BeginSubMenu(_("T&rack Size"));
737  c->AddItem(wxT("FitInWindow"), XXO("&Fit to Width"), FN(OnZoomFit), wxT("Ctrl+F"));
738  c->AddItem(wxT("FitV"), XXO("Fit to &Height"), FN(OnZoomFitV), wxT("Ctrl+Shift+F"));
739  c->AddItem(wxT("CollapseAllTracks"), XXO("&Collapse All Tracks"), FN(OnCollapseAllTracks), wxT("Ctrl+Shift+C"));
740  c->AddItem(wxT("ExpandAllTracks"), XXO("E&xpand Collapsed Tracks"), FN(OnExpandAllTracks), wxT("Ctrl+Shift+X"));
741  c->EndSubMenu();
742 
743  c->BeginSubMenu(_("Sk&ip to"));
744  c->SetLongName( _("Skip to Selection Start"))->AddItem(wxT("SkipSelStart"), XXO("Selection Sta&rt"), FN(OnGoSelStart), wxT("Ctrl+["),
746  c->SetLongName( _("Skip to Selection End"))->AddItem(wxT("SkipSelEnd"), XXO("Selection En&d"), FN(OnGoSelEnd), wxT("Ctrl+]"),
748  c->EndSubMenu();
749 
750  c->AddSeparator();
751 
752  // History window should be available either for UndoAvailableFlag or RedoAvailableFlag,
753  // but we can't make the AddItem flags and mask have both, because they'd both have to be true for the
754  // command to be enabled.
755  // If user has Undone the entire stack, RedoAvailableFlag is on but UndoAvailableFlag is off.
756  // If user has done things but not Undone anything, RedoAvailableFlag is off but UndoAvailableFlag is on.
757  // So in either of those cases, (AudioIONotBusyFlag | UndoAvailableFlag | RedoAvailableFlag) mask
758  // would fail.
759  // The only way to fix this in the current architecture is to hack in special cases for RedoAvailableFlag
760  // in AudacityProject::UpdateMenus() (ugly) and CommandManager::HandleCommandEntry() (*really* ugly --
761  // shouldn't know about particular command names and flags).
762  // Here's the hack that would be necessary in AudacityProject::UpdateMenus(), if somebody decides to do it:
763  // // Because EnableUsingFlags requires all the flag bits match the corresponding mask bits,
764  // // "UndoHistory" specifies only AudioIONotBusyFlag | UndoAvailableFlag, because that
765  // // covers the majority of cases where it should be enabled.
766  // // If history is not empty but we've Undone the whole stack, we also want to enable,
767  // // to show the Redo's on stack.
768  // // "UndoHistory" might already be enabled, but add this check for RedoAvailableFlag.
769  // if (flags & RedoAvailableFlag)
770  // mCommandManager.Enable(wxT("UndoHistory"), true);
771  // So for now, enable the command regardless of stack. It will just show empty sometimes.
772  // FOR REDESIGN, clearly there are some limitations with the flags/mask bitmaps.
773 
774  /* i18n-hint: Clicking this menu item shows the various editing steps that have been taken.*/
775  c->AddItem(wxT("UndoHistory"), XXO("&History..."), FN(OnHistory),
778 
779  c->AddItem(wxT("Karaoke"), XXO("&Karaoke..."), FN(OnKaraoke), LabelTracksExistFlag, LabelTracksExistFlag);
780  c->AddItem(wxT("MixerBoard"), XXO("&Mixer Board..."), FN(OnMixerBoard), PlayableTracksExistFlag, PlayableTracksExistFlag);
781 
782  c->AddSeparator();
783 
785 
786  c->BeginSubMenu(_("&Toolbars"));
787 
788  /* i18n-hint: (verb)*/
789  c->AddItem(wxT("ResetToolbars"), XXO("Reset Toolb&ars"), FN(OnResetToolBars), 0, AlwaysEnabledFlag, AlwaysEnabledFlag);
790  c->AddSeparator();
791 
792  /* i18n-hint: Clicking this menu item shows the toolbar with the big buttons on it (play record etc)*/
793  c->AddCheck(wxT("ShowTransportTB"), XXO("&Transport Toolbar"), FN(OnShowTransportToolBar), 0, AlwaysEnabledFlag, AlwaysEnabledFlag);
794  /* i18n-hint: Clicking this menu item shows a toolbar that has some tools in it*/
795  c->AddCheck(wxT("ShowToolsTB"), XXO("T&ools Toolbar"), FN(OnShowToolsToolBar), 0, AlwaysEnabledFlag, AlwaysEnabledFlag);
796  /* i18n-hint: Clicking this menu item shows the toolbar with the recording level meters*/
797  c->AddCheck(wxT("ShowRecordMeterTB"), XXO("&Recording Meter Toolbar"), FN(OnShowRecordMeterToolBar), 0, AlwaysEnabledFlag, AlwaysEnabledFlag);
798  /* i18n-hint: Clicking this menu item shows the toolbar with the playback level meter*/
799  c->AddCheck(wxT("ShowPlayMeterTB"), XXO("&Playback Meter Toolbar"), FN(OnShowPlayMeterToolBar), 0, AlwaysEnabledFlag, AlwaysEnabledFlag);
800  /* --i18n-hint: Clicking this menu item shows the toolbar which has sound level meters*/
801  //c->AddCheck(wxT("ShowMeterTB"), XXO("Co&mbined Meter Toolbar"), FN(OnShowMeterToolBar), 0, AlwaysEnabledFlag, AlwaysEnabledFlag);
802  /* i18n-hint: Clicking this menu item shows the toolbar with the mixer*/
803  c->AddCheck(wxT("ShowMixerTB"), XXO("Mi&xer Toolbar"), FN(OnShowMixerToolBar), 0, AlwaysEnabledFlag, AlwaysEnabledFlag);
804  /* i18n-hint: Clicking this menu item shows the toolbar for editing*/
805  c->AddCheck(wxT("ShowEditTB"), XXO("&Edit Toolbar"), FN(OnShowEditToolBar), 0, AlwaysEnabledFlag, AlwaysEnabledFlag);
806  /* i18n-hint: Clicking this menu item shows the toolbar for transcription (currently just vary play speed)*/
807  c->AddCheck(wxT("ShowTranscriptionTB"), XXO("Tra&nscription Toolbar"), FN(OnShowTranscriptionToolBar), 0, AlwaysEnabledFlag, AlwaysEnabledFlag);
808  /* i18n-hint: Clicking this menu item shows the toolbar that enables Scrub or Seek playback and Scrub Ruler*/
809  c->AddCheck(wxT("ShowScrubbingTB"), XXO("Scru&b Toolbar"), FN(OnShowScrubbingToolBar), 0, AlwaysEnabledFlag, AlwaysEnabledFlag);
810  /* i18n-hint: Clicking this menu item shows the toolbar that manages devices*/
811  c->AddCheck(wxT("ShowDeviceTB"), XXO("&Device Toolbar"), FN(OnShowDeviceToolBar), 0, AlwaysEnabledFlag, AlwaysEnabledFlag);
812  /* i18n-hint: Clicking this menu item shows the toolbar for selecting a time range of audio*/
813  c->AddCheck(wxT("ShowSelectionTB"), XXO("&Selection Toolbar"), FN(OnShowSelectionToolBar), 0, AlwaysEnabledFlag, AlwaysEnabledFlag);
814 #ifdef EXPERIMENTAL_SPECTRAL_EDITING
815  /* i18n-hint: Clicking this menu item shows the toolbar for selecting a frequency range of audio*/
816  c->AddCheck(wxT("ShowSpectralSelectionTB"), XXO("Spe&ctral Selection Toolbar"), FN(OnShowSpectralSelectionToolBar), 0, AlwaysEnabledFlag, AlwaysEnabledFlag);
817 #endif
818 
819  c->EndSubMenu();
820 
821  c->AddSeparator();
822 
823  c->AddCheck(wxT("ShowExtraMenus"), XXO("&Extra Menus (on/off)"), FN(OnShowExtraMenus),
824  gPrefs->Read(wxT("/GUI/ShowExtraMenus"), 0L), AlwaysEnabledFlag, AlwaysEnabledFlag);
825  c->AddCheck(wxT("ShowClipping"), XXO("&Show Clipping (on/off)"), FN(OnShowClipping),
826  gPrefs->Read(wxT("/GUI/ShowClipping"), 0L), AlwaysEnabledFlag, AlwaysEnabledFlag);
827 #if defined(EXPERIMENTAL_EFFECTS_RACK)
828  c->AddCheck(wxT("ShowEffectsRack"), XXO("Show Effects Rack"), FN(OnShowEffectsRack), 0, AlwaysEnabledFlag, AlwaysEnabledFlag);
829 #endif
830 
831 
832  c->EndMenu();
833 
835  // Transport Menu
837 
838  /*i18n-hint: 'Transport' is the name given to the set of controls that
839  play, record, pause etc. */
840  c->BeginMenu(_("Tra&nsport"));
842  c->BeginSubMenu(_("Pl&aying"));
843  /* i18n-hint: (verb) Start or Stop audio playback*/
844  c->AddItem(wxT("PlayStop"), XXO("Pl&ay/Stop"), FN(OnPlayStop), wxT("Space"));
845  c->AddItem(wxT("PlayStopSelect"), XXO("Play/Stop and &Set Cursor"), FN(OnPlayStopSelect), wxT("X"));
846  c->AddItem(wxT("PlayLooped"), XXO("&Loop Play"), FN(OnPlayLooped), wxT("Shift+Space"),
849  c->AddItem(wxT("Pause"), XXO("&Pause"), FN(OnPause), wxT("P"));
850  c->EndSubMenu();
851 
852  c->BeginSubMenu( _("&Recording"));
855  /* i18n-hint: (verb)*/
856  c->AddItem(wxT("Record1stChoice"), XXO("&Record"), FN(OnRecord), wxT("R"));
857  // The OnRecord2ndChoice function is: if normal record records beside,
858  // it records below, if normal record records below, it records beside.
859  // TODO: Do 'the right thing' with other options like TimerRecord.
860  bool bPreferNewTrack;
861  gPrefs->Read("/GUI/PreferNewTrackRecord",&bPreferNewTrack, false);
862  c->AddItem( wxT("Record2ndChoice"),
863  // Our first choice is bound to R (by default) and gets the prime position.
864  // We supply the name for the 'other one' here. It should be bound to Shift+R
865  (bPreferNewTrack ? _("&Append Record") : _("Record &New Track")), false,
867  wxT("Shift+R")
868  );
869 
870  c->AddItem(wxT("TimerRecord"), XXO("&Timer Record..."), FN(OnTimerRecord), wxT("Shift+T"));
871 
872 #ifdef EXPERIMENTAL_PUNCH_AND_ROLL
873  c->AddItem(wxT("PunchAndRoll"), XXO("Punch and Rol&l Record"), FN(OnPunchAndRoll), wxT("Shift+D"),
876 #endif
877 
878  // JKC: I decided to duplicate this between play and record, rather than put it
879  // at the top level. AddItem can now cope with simple duplicated items.
880  // PRL: This second registration of wxT("Pause"), with unspecified flags,
881  // in fact will use the same flags as in the previous registration.
882  c->AddItem(wxT("Pause"), XXO("&Pause"), FN(OnPause), wxT("P"));
883  c->EndSubMenu();
884 
885  // Scrubbing sub-menu
887 
888  // JKC: ANSWER-ME: How is 'cursor to' different to 'Skip To' and how is it useful?
889  // GA: 'Skip to' moves the viewpoint to center of the track and preserves the
890  // selection. 'Cursor to' does neither. 'Center at' might describe it better than 'Skip'.
891  c->BeginSubMenu(_("&Cursor to"));
892 
893  c->SetLongName( _("Cursor to Selection Start"))->AddItem(wxT("CursSelStart"), XXO("Selection Star&t"), FN(OnCursorSelStart),
895  c->SetLongName( _("Cursor to Selection End"))->AddItem(wxT("CursSelEnd"), XXO("Selection En&d"), FN(OnCursorSelEnd),
897 
898  c->SetLongName( _("Cursor to Track Start"))->AddItem(wxT("CursTrackStart"), XXO("Track &Start"), FN(OnCursorTrackStart), wxT("J"),
900  c->SetLongName( _("Cursor to Track End"))->AddItem(wxT("CursTrackEnd"), XXO("Track &End"), FN(OnCursorTrackEnd), wxT("K"),
902 
903  c->SetLongName( _("Cursor to Prev Clip Boundary"))->AddItem(wxT("CursPrevClipBoundary"), XXO("Pre&vious Clip Boundary"), FN(OnCursorPrevClipBoundary), wxT(""),
905  c->SetLongName( _("Cursor to Next Clip Boundary"))->AddItem(wxT("CursNextClipBoundary"), XXO("Ne&xt Clip Boundary"), FN(OnCursorNextClipBoundary), wxT(""),
907 
908  c->SetLongName( _("Cursor to Project Start"))->AddItem(wxT("CursProjectStart"), XXO("&Project Start"), FN(OnSkipStart), wxT("Home"));
909  c->SetLongName( _("Cursor to Project End"))->AddItem(wxT("CursProjectEnd"), XXO("Project E&nd"), FN(OnSkipEnd), wxT("End"));
910 
911  c->EndSubMenu();
912 
913  c->AddSeparator();
914 
916 
917  c->BeginSubMenu(_("Pla&y Region"));
918 
919  c->AddItem(wxT("LockPlayRegion"), XXO("&Lock"), FN(OnLockPlayRegion),
922  c->AddItem(wxT("UnlockPlayRegion"), XXO("&Unlock"), FN(OnUnlockPlayRegion),
925 
926  c->EndSubMenu();
927 
928  c->AddSeparator();
929 
930  c->AddItem(wxT("RescanDevices"), XXO("R&escan Audio Devices"), FN(OnRescanDevices),
933 
934  c->BeginSubMenu(_("Transport &Options"));
935  // Sound Activated recording options
936  c->AddItem(wxT("SoundActivationLevel"), XXO("Sound Activation Le&vel..."), FN(OnSoundActivated),
939  c->AddCheck(wxT("SoundActivation"), XXO("Sound A&ctivated Recording (on/off)"), FN(OnToggleSoundActivated), 0,
942  c->AddSeparator();
943 
944  c->AddCheck(wxT("PinnedHead"), XXO("Pinned Play/Record &Head (on/off)"),
946  // Switching of scrolling on and off is permitted even during transport
948 
949  c->AddCheck(wxT("Duplex"), XXO("&Overdub (on/off)"), FN(OnTogglePlayRecording), 0,
952  c->AddCheck(wxT("SWPlaythrough"), XXO("So&ftware Playthrough (on/off)"), FN(OnToggleSWPlaythrough), 0,
955 
956 
957 #ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT
958  c->AddCheck(wxT("AutomatedInputLevelAdjustmentOnOff"), XXO("A&utomated Recording Level Adjustment (on/off)"), FN(OnToggleAutomatedInputLevelAdjustment), 0,
961 #endif
962  c->EndSubMenu();
963 
964  c->EndMenu();
965 
967  // Tracks Menu (formerly Project Menu)
969 
970  c->BeginMenu(_("&Tracks"));
972 
974 
975  c->BeginSubMenu(_("Add &New"));
976 
977  c->AddItem(wxT("NewMonoTrack"), XXO("&Mono Track"), FN(OnNewWaveTrack), wxT("Ctrl+Shift+N"));
978  c->AddItem(wxT("NewStereoTrack"), XXO("&Stereo Track"), FN(OnNewStereoTrack));
979  c->AddItem(wxT("NewLabelTrack"), XXO("&Label Track"), FN(OnNewLabelTrack));
980  c->AddItem(wxT("NewTimeTrack"), XXO("&Time Track"), FN(OnNewTimeTrack));
981 
982  c->EndSubMenu();
983 
985 
986  c->AddSeparator();
987 
988  c->BeginSubMenu(_("Mi&x") );
989  {
990  // Stereo to Mono is an oddball command that is also subject to control by the
991  // plug-in manager, as if an effect. Decide whether to show or hide it.
992  const PluginID ID = EffectManager::Get().GetEffectByIdentifier(wxT("StereoToMono"));
993  const PluginDescriptor *plug = PluginManager::Get().GetPlugin(ID);
994  if (plug && plug->IsEnabled())
995  c->AddItem(wxT("Stereo to Mono"), XXO("Mix Stereo Down to &Mono"), FN(OnStereoToMono),
998  }
999  c->AddItem(wxT("MixAndRender"), XXO("Mi&x and Render"), FN(OnMixAndRender),
1002  c->AddItem(wxT("MixAndRenderToNewTrack"), XXO("Mix and Render to Ne&w Track"), FN(OnMixAndRenderToNewTrack), wxT("Ctrl+Shift+M"),
1005  c->EndSubMenu();
1006 
1007  c->AddItem(wxT("Resample"), XXO("&Resample..."), FN(OnResample),
1010 
1011  c->AddSeparator();
1012 
1013  c->AddItem(wxT("RemoveTracks"), XXO("Remo&ve Tracks"), FN(OnRemoveTracks),
1016 
1017  c->AddSeparator();
1018 
1019  c->BeginSubMenu(_("M&ute/Unmute"));
1020  c->AddItem(wxT("MuteAllTracks"), XXO("&Mute All Tracks"), FN(OnMuteAllTracks), wxT("Ctrl+U"));
1021  c->AddItem(wxT("UnmuteAllTracks"), XXO("&Unmute All Tracks"), FN(OnUnmuteAllTracks), wxT("Ctrl+Shift+U"));
1022  c->EndSubMenu();
1023 
1024  c->BeginSubMenu(_("&Pan"));
1025  // As Pan changes are not saved on Undo stack, pan settings for all tracks
1026  // in the project could very easily be lost unless we require the tracks to be selcted.
1028  c->SetLongName( _("Pan Left"))->AddItem(wxT("PanLeft"), XXO("&Left"), FN(OnPanLeft));
1029  c->SetLongName( _("Pan Right"))->AddItem(wxT("PanRight"), XXO("&Right"), FN(OnPanRight));
1030  c->SetLongName( _("Pan Center"))->AddItem(wxT("PanCenter"), XXO("&Center"), FN(OnPanCenter));
1031  c->EndSubMenu();
1032 
1033 
1034  c->AddSeparator();
1035 
1037 
1038  const TranslatedInternalString alignLabelsNoSync[] = {
1039  { wxT("EndToEnd"), _("&Align End to End") },
1040  { wxT("Together"), _("Align &Together") },
1041  };
1042 
1043  const TranslatedInternalString alignLabels[] = {
1044  { wxT("StartToZero"), _("Start to &Zero") },
1045  { wxT("StartToSelStart"), _("Start to &Cursor/Selection Start") },
1046  { wxT("StartToSelEnd"), _("Start to Selection &End") },
1047  { wxT("EndToSelStart"), _("End to Cu&rsor/Selection Start") },
1048  { wxT("EndToSelEnd"), _("End to Selection En&d") },
1049  };
1050  mAlignLabelsCount = sizeof(alignLabels) / sizeof(alignLabels[0]);
1051 
1052  // Calling c->SetCommandFlags() after AddItemList for "Align" and "AlignMove"
1053  // does not correctly set flags for submenus, so do it this way.
1056 
1057  c->BeginSubMenu(_("&Align Tracks"));
1058 
1059  //c->BeginSubMenu(_("Just Move Tracks"));
1060  c->AddItemList(wxT("Align"), alignLabelsNoSync, 2u, FN(OnAlignNoSync));
1061  c->AddSeparator();
1062  c->AddItemList(wxT("Align"), alignLabels, mAlignLabelsCount, FN(OnAlign));
1063  c->AddSeparator();
1064  c->AddCheck(wxT("MoveSelectionWithTracks"), XXO("&Move Selection with Tracks (on/off)"),
1066  gPrefs->Read(wxT("/GUI/MoveSelectionWithTracks"), 0L),
1068  c->EndSubMenu();
1069 
1070 #if 0
1071  // TODO: Can these labels be made clearer? Do we need this sub-menu at all?
1072  c->BeginSubMenu(_("Move Sele&ction and Tracks"));
1073 
1074  c->AddItemList(wxT("AlignMove"), alignLabels, mAlignLabelsCount, FN(OnAlignMoveSel));
1075  c->SetCommandFlags(wxT("AlignMove"),
1078 
1079  c->EndSubMenu();
1080 #endif
1081 
1083 
1084 
1086 
1087 #ifdef EXPERIMENTAL_SCOREALIGN
1088  c->AddItem(wxT("ScoreAlign"), XXO("Synchronize MIDI with Audio"), FN(OnScoreAlign),
1091 #endif // EXPERIMENTAL_SCOREALIGN
1092 
1094 
1095  c->BeginSubMenu(_("S&ort Tracks"));
1096 
1097  c->SetLongName( _("Sort by Time"))->AddItem(wxT("SortByTime"), XXO("By &Start Time"), FN(OnSortTime),
1099  TracksExistFlag);
1100  c->SetLongName( _("Sort by Name"))->AddItem(wxT("SortByName"), XXO("By &Name"), FN(OnSortName),
1102  TracksExistFlag);
1103 
1104  c->EndSubMenu();
1105 
1107 
1108 #ifdef EXPERIMENTAL_SYNC_LOCK
1109  c->AddSeparator();
1110  c->AddCheck(wxT("SyncLock"), XXO("Sync-&Lock Tracks (on/off)"), FN(OnSyncLock),
1111  gPrefs->Read(wxT("/GUI/SyncLockTracks"), 0L),
1113 
1114 #endif
1115 
1116  c->EndMenu();
1117 
1118  // All of this is a bit hacky until we can get more things connected into
1119  // the plugin manager...sorry! :-(
1120 
1121  wxArrayString defaults;
1122 
1124  // Generate Menu
1126 
1127  c->BeginMenu(_("&Generate"));
1129 
1130 #ifdef EXPERIMENTAL_EFFECT_MANAGEMENT
1131  c->AddItem(wxT("ManageGenerators"), XXO("Add / Remove Plug-ins..."), FN(OnManageGenerators));
1132  c->AddSeparator();
1133 #endif
1134 
1135 
1140 
1141  c->EndMenu();
1142 
1144  // Effect Menu
1146 
1147  c->BeginMenu(_("Effe&ct"));
1148 
1149  wxString buildMenuLabel;
1150  if (!mLastEffect.IsEmpty()) {
1151  buildMenuLabel.Printf(_("Repeat %s"),
1152  EffectManager::Get().GetCommandName(mLastEffect));
1153  }
1154  else
1155  buildMenuLabel = _("Repeat Last Effect");
1156 
1157 #ifdef EXPERIMENTAL_EFFECT_MANAGEMENT
1158  c->AddItem(wxT("ManageEffects"), XXO("Add / Remove Plug-ins..."), FN(OnManageEffects));
1159  c->AddSeparator();
1160 #endif
1161 
1162  c->AddItem(wxT("RepeatLastEffect"), buildMenuLabel, false, FN(OnRepeatLastEffect), wxT("Ctrl+R"),
1165 
1166  c->AddSeparator();
1167 
1172 
1173  c->EndMenu();
1174 
1176  // Analyze Menu
1178 
1179  c->BeginMenu(_("&Analyze"));
1180 
1181 #ifdef EXPERIMENTAL_EFFECT_MANAGEMENT
1182  c->AddItem(wxT("ManageAnalyzers"), XXO("Add / Remove Plug-ins..."), FN(OnManageAnalyzers));
1183  c->AddSeparator();
1184 #endif
1185 
1186 
1187  c->AddItem(wxT("ContrastAnalyser"), XXO("Contrast..."), FN(OnContrast), wxT("Ctrl+Shift+T"),
1190  c->AddItem(wxT("PlotSpectrum"), XXO("Plot Spectrum..."), FN(OnPlotSpectrum),
1193 
1198 
1199  c->EndMenu();
1200 
1202  // Tools Menu
1204 
1205  c->BeginMenu(_("T&ools"));
1206 
1207 #ifdef EXPERIMENTAL_EFFECT_MANAGEMENT
1208  c->AddItem(wxT("ManageTools"), XXO("Add / Remove Plug-ins..."), FN(OnManageTools));
1209  //c->AddSeparator();
1210 #endif
1211 
1212  c->AddItem(wxT("ManageMacros"), XXO("&Macros..."), FN(OnManageMacros));
1213 
1214  c->BeginSubMenu(_("&Apply Macro"));
1215  c->AddItem(wxT("ApplyMacrosPalette"), XXO("&Palette..."), FN(OnApplyMacrosPalette));
1216  c->AddSeparator();
1218  c->EndSubMenu();
1219  c->AddSeparator();
1220 
1221  c->AddItem(wxT("FancyScreenshot"), XXO("&Screenshot..."), FN(OnScreenshot));
1222 
1223 // PRL: team consensus for 2.2.0 was, we let end users have this diagnostic,
1224 // as they used to in 1.3.x
1225 //#ifdef IS_ALPHA
1226  // TODO: What should we do here? Make benchmark a plug-in?
1227  // Easy enough to do. We'd call it mod-self-test.
1228  c->AddItem(wxT("Benchmark"), XXO("&Run Benchmark..."), FN(OnBenchmark));
1229 //#endif
1230 
1231 #ifdef IS_ALPHA
1232  c->AddCheck(wxT("SimulateRecordingErrors"),
1233  XXO("Simulate Recording Errors"),
1236  c->AddCheck(wxT("DetectUpstreamDropouts"),
1237  XXO("Detect Upstream Dropouts"),
1240 #endif
1241  c->AddSeparator();
1242 
1247 
1248  c->EndMenu();
1249 
1250 
1251 #ifdef __WXMAC__
1252  // poor imitation of the Mac Windows Menu
1255 
1256  {
1257  c->BeginMenu(_("&Window"));
1258  /* i18n-hint: Standard Macintosh Window menu item: Make (the current
1259  * window) shrink to an icon on the dock */
1260  c->AddItem(wxT("MacMinimize"), XXO("&Minimize"), FN(OnMacMinimize),
1261  wxT("Ctrl+M"), NotMinimizedFlag, NotMinimizedFlag);
1262  /* i18n-hint: Standard Macintosh Window menu item: Make (the current
1263  * window) full sized */
1264  c->AddItem(wxT("MacZoom"), XXO("&Zoom"), FN(OnMacZoom),
1266  c->AddSeparator();
1267  /* i18n-hint: Standard Macintosh Window menu item: Make all project
1268  * windows un-hidden */
1269  c->AddItem(wxT("MacBringAllToFront"),
1270  XXO("&Bring All to Front"), FN(OnMacBringAllToFront),
1272  c->EndMenu();
1273  }
1274 #endif
1275 
1276 
1278 
1279  bool bShowExtraMenus;
1280  gPrefs->Read(wxT("/GUI/ShowExtraMenus"), &bShowExtraMenus, false);
1281  std::unique_ptr<wxMenuBar> menubar2;
1282  if( !bShowExtraMenus )
1283  {
1284  menubar2 = c->AddMenuBar(wxT("ext-menu"));
1285  c->SetOccultCommands(true);
1286  }
1287 
1289  // Ext-Menu
1291 
1292  // i18n-hint: Extra is a menu with extra commands
1293  c->BeginMenu(_("Ext&ra"));
1294 
1296 
1298  c->BeginSubMenu(_("T&ransport"));
1299 
1300  // PlayStop is already in the menus.
1301  /* i18n-hint: (verb) Start playing audio*/
1302  c->AddItem(wxT("Play"), XXO("Pl&ay"), FN(OnPlayStop),
1305  /* i18n-hint: (verb) Stop playing audio*/
1306  c->AddItem(wxT("Stop"), XXO("Sto&p"), FN(OnStop),
1307  AudioIOBusyFlag | CanStopAudioStreamFlag,
1308  AudioIOBusyFlag | CanStopAudioStreamFlag);
1309 
1311 
1312  c->AddItem(wxT("PlayOneSec"), XXO("Play &One Second"), FN(OnPlayOneSecond), wxT("1"),
1315  c->AddItem(wxT("PlayToSelection"), XXO("Play to &Selection"), FN(OnPlayToSelection), wxT("B"),
1318  c->AddItem(wxT("PlayBeforeSelectionStart"), XXO("Play &Before Selection Start"), FN(OnPlayBeforeSelectionStart), wxT("Shift+F5"));
1319  c->AddItem(wxT("PlayAfterSelectionStart"), XXO("Play Af&ter Selection Start"), FN(OnPlayAfterSelectionStart), wxT("Shift+F6"));
1320  c->AddItem(wxT("PlayBeforeSelectionEnd"), XXO("Play Be&fore Selection End"), FN(OnPlayBeforeSelectionEnd), wxT("Shift+F7"));
1321  c->AddItem(wxT("PlayAfterSelectionEnd"), XXO("Play Aft&er Selection End"), FN(OnPlayAfterSelectionEnd), wxT("Shift+F8"));
1322  c->AddItem(wxT("PlayBeforeAndAfterSelectionStart"), XXO("Play Before a&nd After Selection Start"), FN(OnPlayBeforeAndAfterSelectionStart), wxT("Ctrl+Shift+F5"));
1323  c->AddItem(wxT("PlayBeforeAndAfterSelectionEnd"), XXO("Play Before an&d After Selection End"), FN(OnPlayBeforeAndAfterSelectionEnd), wxT("Ctrl+Shift+F7"));
1324  c->AddItem(wxT("PlayCutPreview"), XXO("Play C&ut Preview"), FN(OnPlayCutPreview), wxT("C"),
1327  c->EndSubMenu();
1328 
1330 
1332  c->BeginSubMenu(_("T&ools"));
1333 
1334  c->AddItem(wxT("SelectTool"), XXO("&Selection Tool"), FN(OnSelectTool), wxT("F1"));
1335  c->AddItem(wxT("EnvelopeTool"), XXO("&Envelope Tool"), FN(OnEnvelopeTool), wxT("F2"));
1336  c->AddItem(wxT("DrawTool"), XXO("&Draw Tool"), FN(OnDrawTool), wxT("F3"));
1337  c->AddItem(wxT("ZoomTool"), XXO("&Zoom Tool"), FN(OnZoomTool), wxT("F4"));
1338  c->AddItem(wxT("TimeShiftTool"), XXO("&Time Shift Tool"), FN(OnTimeShiftTool), wxT("F5"));
1339  c->AddItem(wxT("MultiTool"), XXO("&Multi Tool"), FN(OnMultiTool), wxT("F6"));
1340 
1341  c->AddItem(wxT("PrevTool"), XXO("&Previous Tool"), FN(OnPrevTool), wxT("A"));
1342  c->AddItem(wxT("NextTool"), XXO("&Next Tool"), FN(OnNextTool), wxT("D"));
1343  c->EndSubMenu();
1344 
1346 
1348  c->BeginSubMenu(_("Mi&xer"));
1349 
1350  c->AddItem(wxT("OutputGain"), XXO("Ad&just Playback Volume..."), FN(OnOutputGain));
1351  c->AddItem(wxT("OutputGainInc"), XXO("&Increase Playback Volume"), FN(OnOutputGainInc));
1352  c->AddItem(wxT("OutputGainDec"), XXO("&Decrease Playback Volume"), FN(OnOutputGainDec));
1353  c->AddItem(wxT("InputGain"), XXO("Adj&ust Recording Volume..."), FN(OnInputGain));
1354  c->AddItem(wxT("InputGainInc"), XXO("I&ncrease Recording Volume"), FN(OnInputGainInc));
1355  c->AddItem(wxT("InputGainDec"), XXO("D&ecrease Recording Volume"), FN(OnInputGainDec));
1356  c->EndSubMenu();
1357 
1359 
1361  c->BeginSubMenu(_("&Edit"));
1362 
1363  c->AddItem(wxT("DeleteKey"), XXO("&Delete Key"), FN(OnDelete), wxT("Backspace"),
1366 
1367  c->AddItem(wxT("DeleteKey2"), XXO("Delete Key&2"), FN(OnDelete), wxT("Delete"),
1370  c->EndSubMenu();
1371 
1373 
1375  c->BeginSubMenu(_("Transcri&ption"));
1376 
1377  c->AddItem(wxT("PlayAtSpeed"), XXO("Pl&ay-at-Speed"), FN(OnPlayAtSpeed));
1378  c->AddItem(wxT("PlayAtSpeedLooped"), XXO("&Loop Play-at-Speed"), FN(OnPlayAtSpeedLooped));
1379  c->AddItem(wxT("PlayAtSpeedCutPreview"), XXO("Play C&ut Preview-at-Speed"), FN(OnPlayAtSpeedCutPreview));
1380  c->AddItem(wxT("SetPlaySpeed"), XXO("Ad&just Playback Speed..."), FN(OnSetPlaySpeed));
1381  c->AddItem(wxT("PlaySpeedInc"), XXO("&Increase Playback Speed"), FN(OnPlaySpeedInc));
1382  c->AddItem(wxT("PlaySpeedDec"), XXO("&Decrease Playback Speed"), FN(OnPlaySpeedDec));
1383 
1384  // These were on the original transcription toolbar. But they are not on the
1385  // shortened one.
1386  c->AddItem(wxT("MoveToPrevLabel"), XXO("Move to &Previous Label"), FN(OnMoveToPrevLabel), wxT("Alt+Left"),
1388  c->AddItem(wxT("MoveToNextLabel"), XXO("Move to &Next Label"), FN(OnMoveToNextLabel), wxT("Alt+Right"),
1390  c->EndSubMenu();
1391 
1393 
1394  c->SetDefaultFlags(AudioIOBusyFlag, AudioIOBusyFlag);
1395  c->BeginSubMenu(_("See&k"));
1396 
1397  c->AddItem(wxT("SeekLeftShort"), XXO("Short Seek &Left During Playback"), FN(OnSeekLeftShort), wxT("Left\tallowDup"));
1398  c->AddItem(wxT("SeekRightShort"), XXO("Short Seek &Right During Playback"), FN(OnSeekRightShort), wxT("Right\tallowDup"));
1399  c->AddItem(wxT("SeekLeftLong"), XXO("Long Seek Le&ft During Playback"), FN(OnSeekLeftLong), wxT("Shift+Left\tallowDup"));
1400  c->AddItem(wxT("SeekRightLong"), XXO("Long Seek Rig&ht During Playback"), FN(OnSeekRightLong), wxT("Shift+Right\tallowDup"));
1401  c->EndSubMenu();
1402 
1404 
1406  c->BeginSubMenu(_("De&vice"));
1407 
1408  c->AddItem(wxT("InputDevice"), XXO("Change &Recording Device..."), FN(OnInputDevice), wxT("Shift+I"),
1411  c->AddItem(wxT("OutputDevice"), XXO("Change &Playback Device..."), FN(OnOutputDevice), wxT("Shift+O"),
1414  c->AddItem(wxT("AudioHost"), XXO("Change Audio &Host..."), FN(OnAudioHost), wxT("Shift+H"),
1417  c->AddItem(wxT("InputChannels"), XXO("Change Recording Cha&nnels..."), FN(OnInputChannels), wxT("Shift+N"),
1420  c->EndSubMenu();
1421 
1423 
1425  c->BeginSubMenu(_("&Selection"));
1426 
1427  c->AddItem(wxT("SnapToOff"), XXO("Snap-To &Off"), FN(OnSnapToOff));
1428  c->AddItem(wxT("SnapToNearest"), XXO("Snap-To &Nearest"), FN(OnSnapToNearest));
1429  c->AddItem(wxT("SnapToPrior"), XXO("Snap-To &Prior"), FN(OnSnapToPrior));
1430 
1431  c->AddItem(wxT("SelStart"), XXO("Selection to &Start"), FN(OnSelToStart), wxT("Shift+Home"));
1432  c->AddItem(wxT("SelEnd"), XXO("Selection to En&d"), FN(OnSelToEnd), wxT("Shift+End"));
1433  c->AddItem(wxT("SelExtLeft"), XXO("Selection Extend &Left"), FN(OnSelExtendLeft), wxT("Shift+Left\twantKeyup\tallowDup"),
1436  c->AddItem(wxT("SelExtRight"), XXO("Selection Extend &Right"), FN(OnSelExtendRight), wxT("Shift+Right\twantKeyup\tallowDup"),
1439 
1440  c->AddItem(wxT("SelSetExtLeft"), XXO("Set (or Extend) Le&ft Selection"), FN(OnSelSetExtendLeft),
1443  c->AddItem(wxT("SelSetExtRight"), XXO("Set (or Extend) Rig&ht Selection"), FN(OnSelSetExtendRight),
1446 
1447  c->AddItem(wxT("SelCntrLeft"), XXO("Selection Contract L&eft"), FN(OnSelContractLeft), wxT("Ctrl+Shift+Right\twantKeyup"),
1450  c->AddItem(wxT("SelCntrRight"), XXO("Selection Contract R&ight"), FN(OnSelContractRight), wxT("Ctrl+Shift+Left\twantKeyup"),
1453 
1454  c->EndSubMenu();
1455 
1456 
1458  c->AddSeparator();
1459 
1460  c->AddGlobalCommand(wxT("PrevWindow"), XXO("Move Backward Through Active Windows"), FN(PrevWindow), wxT("Alt+Shift+F6"));
1461  c->AddGlobalCommand(wxT("NextWindow"), XXO("Move Forward Through Active Windows"), FN(NextWindow), wxT("Alt+F6"));
1462 
1464 
1466  c->BeginSubMenu(_("F&ocus"));
1467 
1468  c->AddItem(wxT("PrevFrame"), XXO("Move &Backward from Toolbars to Tracks"), FN(PrevFrame), wxT("Ctrl+Shift+F6"));
1469  c->AddItem(wxT("NextFrame"), XXO("Move F&orward from Toolbars to Tracks"), FN(NextFrame), wxT("Ctrl+F6"));
1470 
1473  c->AddItem(wxT("PrevTrack"), XXO("Move Focus to &Previous Track"), FN(OnCursorUp), wxT("Up"));
1474  c->AddItem(wxT("NextTrack"), XXO("Move Focus to &Next Track"), FN(OnCursorDown), wxT("Down"));
1475  c->AddItem(wxT("FirstTrack"), XXO("Move Focus to &First Track"), FN(OnFirstTrack), wxT("Ctrl+Home"));
1476  c->AddItem(wxT("LastTrack"), XXO("Move Focus to &Last Track"), FN(OnLastTrack), wxT("Ctrl+End"));
1477 
1478  c->AddItem(wxT("ShiftUp"), XXO("Move Focus to P&revious and Select"), FN(OnShiftUp), wxT("Shift+Up"));
1479  c->AddItem(wxT("ShiftDown"), XXO("Move Focus to N&ext and Select"), FN(OnShiftDown), wxT("Shift+Down"));
1480 
1481  c->AddItem(wxT("Toggle"), XXO("&Toggle Focused Track"), FN(OnToggle), wxT("Return"));
1482  c->AddItem(wxT("ToggleAlt"), XXO("Toggle Focuse&d Track"), FN(OnToggle), wxT("NUMPAD_ENTER"));
1483  c->EndSubMenu();
1484 
1486 
1488  c->BeginSubMenu(_("&Cursor"));
1489 
1490  c->AddItem(wxT("CursorLeft"), XXO("Cursor &Left"), FN(OnCursorLeft), wxT("Left\twantKeyup\tallowDup"),
1493  c->AddItem(wxT("CursorRight"), XXO("Cursor &Right"), FN(OnCursorRight), wxT("Right\twantKeyup\tallowDup"),
1496  c->AddItem(wxT("CursorShortJumpLeft"), XXO("Cursor Sh&ort Jump Left"), FN(OnCursorShortJumpLeft), wxT(","),
1499  c->AddItem(wxT("CursorShortJumpRight"), XXO("Cursor Shor&t Jump Right"), FN(OnCursorShortJumpRight), wxT("."),
1502  c->AddItem(wxT("CursorLongJumpLeft"), XXO("Cursor Long J&ump Left"), FN(OnCursorLongJumpLeft), wxT("Shift+,"),
1505  c->AddItem(wxT("CursorLongJumpRight"), XXO("Cursor Long Ju&mp Right"), FN(OnCursorLongJumpRight), wxT("Shift+."),
1508 
1509  c->AddItem(wxT("ClipLeft"), XXO("Clip L&eft"), FN(OnClipLeft), wxT("\twantKeyup"),
1512  c->AddItem(wxT("ClipRight"), XXO("Clip Rig&ht"), FN(OnClipRight), wxT("\twantKeyup"),
1515  c->EndSubMenu();
1516 
1518 
1520  c->BeginSubMenu(_("&Track"));
1521 
1522  c->AddItem(wxT("TrackPan"), XXO("Change P&an on Focused Track..."), FN(OnTrackPan), wxT("Shift+P"),
1525  c->AddItem(wxT("TrackPanLeft"), XXO("Pan &Left on Focused Track"), FN(OnTrackPanLeft), wxT("Alt+Shift+Left"),
1528  c->AddItem(wxT("TrackPanRight"), XXO("Pan &Right on Focused Track"), FN(OnTrackPanRight), wxT("Alt+Shift+Right"),
1531  c->AddItem(wxT("TrackGain"), XXO("Change Gai&n on Focused Track..."), FN(OnTrackGain), wxT("Shift+G"),
1534  c->AddItem(wxT("TrackGainInc"), XXO("&Increase Gain on Focused Track"), FN(OnTrackGainInc), wxT("Alt+Shift+Up"),
1537  c->AddItem(wxT("TrackGainDec"), XXO("&Decrease Gain on Focused Track"), FN(OnTrackGainDec), wxT("Alt+Shift+Down"),
1540  c->AddItem(wxT("TrackMenu"), XXO("Op&en Menu on Focused Track..."), FN(OnTrackMenu), wxT("Shift+M\tskipKeydown"),
1543  c->AddItem(wxT("TrackMute"), XXO("M&ute/Unmute Focused Track"), FN(OnTrackMute), wxT("Shift+U"),
1546  c->AddItem(wxT("TrackSolo"), XXO("&Solo/Unsolo Focused Track"), FN(OnTrackSolo), wxT("Shift+S"),
1549  c->AddItem(wxT("TrackClose"), XXO("&Close Focused Track"), FN(OnTrackClose), wxT("Shift+C"),
1552  c->AddItem(wxT("TrackMoveUp"), XXO("Move Focused Track U&p"), FN(OnTrackMoveUp),
1555  c->AddItem(wxT("TrackMoveDown"), XXO("Move Focused Track Do&wn"), FN(OnTrackMoveDown),
1558  c->AddItem(wxT("TrackMoveTop"), XXO("Move Focused Track to T&op"), FN(OnTrackMoveTop),
1561  c->AddItem(wxT("TrackMoveBottom"), XXO("Move Focused Track to &Bottom"), FN(OnTrackMoveBottom),
1564  c->EndSubMenu();
1565 
1566  // These are the more useful to VI user Scriptables.
1567  // i18n-hint: Scriptables are commands normally used from Python, Perl etc.
1568  c->BeginSubMenu(_("&Scriptables I"));
1569 
1570  // Note that the PLUGIN_SYMBOL must have a space between words,
1571  // whereas the short-form used here must not.
1572  // (So if you did write "CompareAudio" for the PLUGIN_SYMBOL name, then
1573  // you would have to use "Compareaudio" here.)
1574 
1575  c->AddItem(wxT("SelectTime"), XXO("Select Time..."), FN(OnAudacityCommand),
1577  c->AddItem(wxT("SelectFrequencies"), XXO("Select Frequencies..."), FN(OnAudacityCommand),
1579  c->AddItem(wxT("SelectTracks"), XXO("Select Tracks..."), FN(OnAudacityCommand),
1581 
1582  c->AddItem(wxT("SetTrackStatus"), XXO("Set Track Status..."), FN(OnAudacityCommand),
1584  c->AddItem(wxT("SetTrackAudio"), XXO("Set Track Audio..."), FN(OnAudacityCommand),
1586  c->AddItem(wxT("SetTrackVisuals"), XXO("Set Track Visuals..."), FN(OnAudacityCommand),
1588 
1589 
1590  c->AddItem(wxT("GetPreference"), XXO("Get Preference..."), FN(OnAudacityCommand),
1592  c->AddItem(wxT("SetPreference"), XXO("Set Preference..."), FN(OnAudacityCommand),
1594  c->AddItem(wxT("SetClip"), XXO("Set Clip..."), FN(OnAudacityCommand),
1596  c->AddItem(wxT("SetEnvelope"), XXO("Set Envelope..."), FN(OnAudacityCommand),
1598  c->AddItem(wxT("SetLabel"), XXO("Set Label..."), FN(OnAudacityCommand),
1600  c->AddItem(wxT("SetProject"), XXO("Set Project..."), FN(OnAudacityCommand),
1602 
1603  c->EndSubMenu();
1604  // Less useful to VI users.
1605  c->BeginSubMenu(_("Scripta&bles II"));
1606 
1607  c->AddItem(wxT("Select"), XXO("Select..."), FN(OnAudacityCommand),
1609  c->AddItem(wxT("SetTrack"), XXO("Set Track..."), FN(OnAudacityCommand),
1611  c->AddItem(wxT("GetInfo"), XXO("Get Info..."), FN(OnAudacityCommand),
1613  c->AddItem(wxT("Message"), XXO("Message..."), FN(OnAudacityCommand),
1615  c->AddItem(wxT("Help"), XXO("Help..."), FN(OnAudacityCommand),
1617 
1618  c->AddItem(wxT("Import2"), XXO("Import..."), FN(OnAudacityCommand),
1620  c->AddItem(wxT("Export2"), XXO("Export..."), FN(OnAudacityCommand),
1622  c->AddItem(wxT("OpenProject2"), XXO("Open Project..."), FN(OnAudacityCommand),
1624  c->AddItem(wxT("SaveProject2"), XXO("Save Project..."), FN(OnAudacityCommand),
1626 
1627  c->AddItem(wxT("Drag"), XXO("Move Mouse..."), FN(OnAudacityCommand),
1629  c->AddItem(wxT("CompareAudio"), XXO("Compare Audio..."), FN(OnAudacityCommand),
1631  // i18n-hint: Screenshot in the help menu has a much bigger dialog.
1632  c->AddItem(wxT("Screenshot"), XXO("Screenshot (short format)..."), FN(OnAudacityCommand),
1634 
1635 
1636  c->EndSubMenu();
1637 
1638 
1639  // Accel key is not bindable.
1640  c->AddItem(wxT("FullScreenOnOff"), XXO("&Full Screen (on/off)"), FN(OnFullScreen),
1641 #ifdef __WXMAC__
1642  wxT("Ctrl+/"),
1643 #else
1644  wxT("F11"),
1645 #endif
1646  AlwaysEnabledFlag, AlwaysEnabledFlag,
1647  wxTopLevelWindow::IsFullScreen() ? 1:0); // Check Mark.
1648 
1649 #ifdef __WXMAC__
1650  /* i18n-hint: Shrink all project windows to icons on the Macintosh tooldock */
1651  c->AddItem(wxT("MacMinimizeAll"), XXO("Minimize All Projects"),
1652  FN(OnMacMinimizeAll), wxT("Ctrl+Alt+M"),
1653  AlwaysEnabledFlag, AlwaysEnabledFlag);
1654 #endif
1655 
1656 
1657 
1658  c->EndMenu();
1659 
1660 
1661 
1662  if (!bShowExtraMenus)
1663  {
1664  c->SwapMenuBars();
1665  c->SetOccultCommands(false);
1666  }
1667 
1669  // Help Menu
1671 
1672 #ifdef __WXMAC__
1673  wxGetApp().s_macHelpMenuTitleName = _("&Help");
1674 #endif
1675 
1676  c->BeginMenu(_("&Help"));
1677  c->SetDefaultFlags(AlwaysEnabledFlag, AlwaysEnabledFlag);
1678 
1679  // DA: Emphasise it is the Audacity Manual (No separate DA manual).
1680 #ifdef EXPERIMENTAL_DA
1681  // 'Getting Started' rather than 'Quick Help' for DarkAudacity.
1682  // At the moment the video tutorials are aspirational (aka do not exist yet).
1683  // Emphasise that manual is for Audacity, not DarkAudacity.
1684  c->AddItem(wxT("QuickHelp"), XXO("&Getting Started"), FN(OnQuickHelp));
1685  c->AddItem(wxT("Manual"), XXO("Audacity &Manual"), FN(OnManual));
1686 #else
1687  c->AddItem(wxT("QuickHelp"), XXO("&Quick Help..."), FN(OnQuickHelp));
1688  c->AddItem(wxT("Manual"), XXO("&Manual..."), FN(OnManual));
1689 #endif
1690  c->AddSeparator();
1691 
1692  c->BeginSubMenu(_("&Diagnostics"));
1693  c->AddItem(wxT("DeviceInfo"), XXO("Au&dio Device Info..."), FN(OnAudioDeviceInfo),
1696 #ifdef EXPERIMENTAL_MIDI_OUT
1697  c->AddItem(wxT("MidiDeviceInfo"), XXO("&MIDI Device Info..."), FN(OnMidiDeviceInfo),
1700 #endif
1701 
1702  c->AddItem(wxT("Log"), XXO("Show &Log..."), FN(OnShowLog));
1703 
1704 #if defined(EXPERIMENTAL_CRASH_REPORT)
1705  c->AddItem(wxT("CrashReport"), XXO("&Generate Support Data..."), FN(OnCrashReport));
1706 #endif
1707  c->AddItem(wxT("CheckDeps"), XXO("Chec&k Dependencies..."), FN(OnCheckDependencies),
1709  c->EndSubMenu();
1710 
1711 #ifndef __WXMAC__
1712  c->AddSeparator();
1713 #endif
1714 
1715  // DA: Does not fully support update checking.
1716 #ifndef EXPERIMENTAL_DA
1717  c->AddItem(wxT("Updates"), XXO("&Check for Updates..."), FN(OnCheckForUpdates));
1718 #endif
1719  c->AddItem(wxT("About"), XXO("&About Audacity..."), FN(OnAbout));
1720 
1721  c->EndMenu();
1722 
1724 
1725 
1726 
1727 
1728 
1729  SetMenuBar(menubar.release());
1730  // Bug 143 workaround.
1731  // The bug is in wxWidgets. For a menu that has scrollers, the
1732  // scrollers have an ID of 0 (not wxID_NONE which is -3).
1733  // Therefore wxWidgets attempts to find a help string. See
1734  // wxFrameBase::ShowMenuHelp(int menuId)
1735  // It finds a bogus automatic help string of "Recent &Files"
1736  // from that submenu.
1737  // So we set the help string for command with Id 0 to empty.
1738  mRecentFilesMenu->GetParent()->SetHelpString( 0, "" );
1739  }
1740 
1741 
1742 
1744 
1745 #if defined(__WXDEBUG__)
1746 // c->CheckDups();
1747 #endif
1748 }
1749 
1750 #undef XXO
1751 
1752 
1753 
1755 {
1756  wxArrayString names = MacroCommands::GetNames();
1757  int i;
1758 
1759  for (i = 0; i < (int)names.GetCount(); i++) {
1760  wxString MacroID = ApplyMacroDialog::MacroIdOfName( names[i] );
1761  c->AddItem(MacroID, names[i], false, FN(OnApplyMacroDirectly),
1762  flags,
1763  flags);
1764  }
1765 
1766 }
1767 
1768 
1773  EffectType type,
1774  CommandFlag batchflags,
1775  CommandFlag realflags)
1776 {
1778 
1779  std::vector<const PluginDescriptor*> defplugs;
1780  std::vector<const PluginDescriptor*> optplugs;
1781 
1782  const PluginDescriptor *plug = pm.GetFirstPluginForEffectType(type);
1783  while (plug)
1784  {
1785  if ( !plug->IsEnabled() ){
1786  ;// don't add to menus!
1787  }
1788  else if (plug->IsEffectDefault()
1789 #ifdef EXPERIMENTAL_DA
1790  // Move Nyquist prompt into nyquist group.
1791  && (plug->GetSymbol() != IdentInterfaceSymbol("Nyquist Effects Prompt"))
1792  && (plug->GetSymbol() != IdentInterfaceSymbol("Nyquist Tools Prompt"))
1793 #endif
1794  )
1795  defplugs.push_back(plug);
1796  else
1797  optplugs.push_back(plug);
1798  plug = pm.GetNextPluginForEffectType(type);
1799  }
1800 
1801  wxString groupby = gPrefs->Read(wxT("/Effects/GroupBy"), wxT("name"));
1802 
1803  using Comparator = bool(*)(const PluginDescriptor*, const PluginDescriptor*);
1804  Comparator comp1, comp2;
1805  if (groupby == wxT("sortby:name"))
1806  comp1 = comp2 = SortEffectsByName;
1807  else if (groupby == wxT("sortby:publisher:name"))
1809  else if (groupby == wxT("sortby:type:name"))
1811  else if (groupby == wxT("groupby:publisher"))
1812  comp1 = comp2 = SortEffectsByPublisher;
1813  else if (groupby == wxT("groupby:type"))
1814  comp1 = comp2 = SortEffectsByType;
1815  else // name
1816  comp1 = comp2 = SortEffectsByName;
1817 
1818  std::sort( defplugs.begin(), defplugs.end(), comp1 );
1819  std::sort( optplugs.begin(), optplugs.end(), comp2 );
1820 
1821  AddEffectMenuItems(c, defplugs, batchflags, realflags, true);
1822 
1823  if (defplugs.size() && optplugs.size())
1824  {
1825  c->AddSeparator();
1826  }
1827 
1828  AddEffectMenuItems(c, optplugs, batchflags, realflags, false);
1829 
1830  return;
1831 }
1832 
1834  std::vector<const PluginDescriptor*> & plugs,
1835  CommandFlag batchflags,
1836  CommandFlag realflags,
1837  bool isDefault)
1838 {
1839  size_t pluginCnt = plugs.size();
1840 
1841  wxString groupBy = gPrefs->Read(wxT("/Effects/GroupBy"), wxT("name"));
1842 
1843  bool grouped = false;
1844  if (groupBy.StartsWith(wxT("groupby")))
1845  {
1846  grouped = true;
1847  }
1848 
1849  std::vector<bool> vHasDialog;
1850  wxArrayString groupNames;
1851  PluginIDList groupPlugs;
1852  std::vector<CommandFlag> groupFlags;
1853  if (grouped)
1854  {
1855  wxString last;
1856  wxString current;
1857 
1858  for (size_t i = 0; i < pluginCnt; i++)
1859  {
1860  const PluginDescriptor *plug = plugs[i];
1861 
1862  bool hasDialog = plug->GetSymbol().Msgid().Contains("...");
1863  auto name = plug->GetSymbol().Translation();
1864 
1865  if (plug->IsEffectInteractive())
1866  {
1867  name += wxT("...");
1868  }
1869 
1870  if (groupBy == wxT("groupby:publisher"))
1871  {
1872  current = EffectManager::Get().GetVendorName(plug->GetID());
1873  if (current.IsEmpty())
1874  {
1875  current = _("Unknown");
1876  }
1877  }
1878  else if (groupBy == wxT("groupby:type"))
1879  {
1880  current = EffectManager::Get().GetEffectFamilyName(plug->GetID());
1881  if (current.IsEmpty())
1882  {
1883  current = _("Unknown");
1884  }
1885  }
1886 
1887  if (current != last)
1888  {
1889  bool bInSubmenu = !last.IsEmpty() && (groupNames.Count() > 1);
1890  if( bInSubmenu)
1891  c->BeginSubMenu(last);
1892 
1893  AddEffectMenuItemGroup(c, groupNames, vHasDialog,
1894  groupPlugs, groupFlags, isDefault);
1895 
1896  if (bInSubmenu)
1897  c->EndSubMenu();
1898 
1899  groupNames.Clear();
1900  vHasDialog.clear();
1901  groupPlugs.Clear();
1902  groupFlags.clear();
1903  last = current;
1904  }
1905 
1906  groupNames.Add(name);
1907  vHasDialog.push_back(hasDialog);
1908  groupPlugs.Add(plug->GetID());
1909  groupFlags.push_back(plug->IsEffectRealtime() ? realflags : batchflags);
1910  }
1911 
1912  if (groupNames.GetCount() > 0)
1913  {
1914  bool bInSubmenu = groupNames.Count() > 1;
1915  if (bInSubmenu)
1916  c->BeginSubMenu(current);
1917 
1918  AddEffectMenuItemGroup(c, groupNames, vHasDialog, groupPlugs, groupFlags, isDefault);
1919 
1920  if (bInSubmenu)
1921  c->EndSubMenu();
1922  }
1923  }
1924  else
1925  {
1926  for (size_t i = 0; i < pluginCnt; i++)
1927  {
1928  const PluginDescriptor *plug = plugs[i];
1929 
1930  bool hasDialog = plug->GetSymbol().Msgid().Contains("...");
1931  auto name = plug->GetSymbol().Translation();
1932 
1933  if (plug->IsEffectInteractive())
1934  {
1935  name += wxT("...");
1936  }
1937 
1938  wxString group = wxEmptyString;
1939  if (groupBy == wxT("sortby:publisher:name"))
1940  {
1941  group = EffectManager::Get().GetVendorName(plug->GetID());
1942  }
1943  else if (groupBy == wxT("sortby:type:name"))
1944  {
1945  group = EffectManager::Get().GetEffectFamilyName(plug->GetID());
1946  }
1947 
1948  if (plug->IsEffectDefault())
1949  {
1950  group = wxEmptyString;
1951  }
1952 
1953  if (!group.IsEmpty())
1954  {
1955  group += wxT(": ");
1956  }
1957 
1958  groupNames.Add(group + name);
1959  vHasDialog.push_back(hasDialog);
1960  groupPlugs.Add(plug->GetID());
1961  groupFlags.push_back(plug->IsEffectRealtime() ? realflags : batchflags);
1962  }
1963 
1964  if (groupNames.GetCount() > 0)
1965  {
1966  AddEffectMenuItemGroup(c, groupNames, vHasDialog, groupPlugs, groupFlags, isDefault);
1967  }
1968 
1969  }
1970 
1971  return;
1972 }
1973 
1975  const wxArrayString & names,
1976  const std::vector<bool> &vHasDialog,
1977  const PluginIDList & plugs,
1978  const std::vector<CommandFlag> & flags,
1979  bool isDefault)
1980 {
1981  int namesCnt = (int) names.GetCount();
1982  int perGroup;
1983 
1984 #if defined(__WXGTK__)
1985  gPrefs->Read(wxT("/Effects/MaxPerGroup"), &perGroup, 15);
1986 #else
1987  gPrefs->Read(wxT("/Effects/MaxPerGroup"), &perGroup, 0);
1988 #endif
1989 
1990  int groupCnt = namesCnt;
1991  for (int i = 0; i < namesCnt; i++)
1992  {
1993  while (i + 1 < namesCnt && names[i].IsSameAs(names[i + 1]))
1994  {
1995  i++;
1996  groupCnt--;
1997  }
1998  }
1999 
2000  // The "default" effects shouldn't be broken into subgroups
2001  if (namesCnt > 0 && isDefault)
2002  {
2003  perGroup = 0;
2004  }
2005 
2006  int max = perGroup;
2007  int items = perGroup;
2008 
2009  if (max > groupCnt)
2010  {
2011  max = 0;
2012  }
2013 
2014  int groupNdx = 0;
2015  for (int i = 0; i < namesCnt; i++)
2016  {
2017  if (max > 0 && items == max)
2018  {
2019  int end = groupNdx + max;
2020  if (end + 1 > groupCnt)
2021  {
2022  end = groupCnt;
2023  }
2024  c->BeginSubMenu(wxString::Format(_("Plug-in %d to %d"),
2025  groupNdx + 1,
2026  end));
2027  }
2028 
2029  if (i + 1 < namesCnt && names[i].IsSameAs(names[i + 1]))
2030  {
2031  wxString name = names[i];
2032  c->BeginSubMenu(name);
2033  while (i < namesCnt && names[i].IsSameAs(name))
2034  {
2035  const PluginDescriptor *plug = PluginManager::Get().GetPlugin(plugs[i]);
2036  wxString item = plug->GetPath();
2037  if( plug->GetPluginType() == PluginTypeEffect )
2038  c->AddItem(item,
2039  item,
2040  item.Contains("..."),
2041  FN(OnEffect),
2042  flags[i],
2043  flags[i], true, plugs[i]);
2044 
2045  i++;
2046  }
2047  c->EndSubMenu();
2048  i--;
2049  }
2050  else
2051  {
2052  const PluginDescriptor *plug = PluginManager::Get().GetPlugin(plugs[i]);
2053  if( plug->GetPluginType() == PluginTypeEffect )
2054  c->AddItem(names[i],
2055  names[i],
2056  vHasDialog[i],
2057  FN(OnEffect),
2058  flags[i],
2059  flags[i], true, plugs[i]);
2060  }
2061 
2062  if (max > 0)
2063  {
2064  groupNdx++;
2065  items--;
2066  if (items == 0 || i + 1 == namesCnt)
2067  {
2068  c->EndSubMenu();
2069  items = max;
2070  }
2071  }
2072  }
2073 
2074  return;
2075 }
2076 
2077 #undef FN
2078 
2080 {
2081  // Recent Files and Recent Projects menus
2082 
2083 #ifdef __WXMAC__
2084  /* i18n-hint: This is the name of the menu item on Mac OS X only */
2085  mRecentFilesMenu = c->BeginSubMenu(_("Open Recent"));
2086 #else
2087  /* i18n-hint: This is the name of the menu item on Windows and Linux */
2088  mRecentFilesMenu = c->BeginSubMenu(_("Recent &Files"));
2089 #endif
2090 
2093 
2094  c->EndSubMenu();
2095 
2096 }
2097 
2099 {
2100  wxString desc;
2101  int cur = GetUndoManager()->GetCurrentState();
2102 
2103  if (GetUndoManager()->UndoAvailable()) {
2104  GetUndoManager()->GetShortDescription(cur, &desc);
2105  mCommandManager.Modify(wxT("Undo"),
2106  wxString::Format(_("&Undo %s"),
2107  desc));
2108  mCommandManager.Enable(wxT("Undo"), this->UndoAvailable());
2109  }
2110  else {
2111  mCommandManager.Modify(wxT("Undo"),
2112  _("&Undo"));
2113  }
2114 
2115  if (GetUndoManager()->RedoAvailable()) {
2116  GetUndoManager()->GetShortDescription(cur+1, &desc);
2117  mCommandManager.Modify(wxT("Redo"),
2118  wxString::Format(_("&Redo %s"),
2119  desc));
2120  mCommandManager.Enable(wxT("Redo"), this->RedoAvailable());
2121  }
2122  else {
2123  mCommandManager.Modify(wxT("Redo"),
2124  _("&Redo"));
2125  mCommandManager.Enable(wxT("Redo"), false);
2126  }
2127 }
2128 
2130 {
2131  // On OSX, we can't rebuild the menus while a modal dialog is being shown
2132  // since the enabled state for menus like Quit and Preference gets out of
2133  // sync with wxWidgets idea of what it should be.
2134 #if defined(__WXMAC__) && defined(__WXDEBUG__)
2135  {
2136  wxDialog *dlg = wxDynamicCast(wxGetTopLevelParent(FindFocus()), wxDialog);
2137  wxASSERT((!dlg || !dlg->IsModal()));
2138  }
2139 #endif
2140 
2141  // Allow FileHistory to remove its own menu
2143 
2144  // Delete the menus, since we will soon recreate them.
2145  // Rather oddly, the menus don't vanish as a result of doing this.
2146  {
2147  std::unique_ptr<wxMenuBar> menuBar{ GetMenuBar() };
2148  DetachMenuBar();
2149  // menuBar gets deleted here
2150  }
2151 
2153 
2155 
2157 }
2158 
2160 {
2161 }
2162 
2164 {
2165  wxWindow *w = FindFocus();
2166 
2167  while (w && mToolManager && mTrackPanel) {
2168  if (w == mToolManager->GetTopDock()) {
2169  return TopDockHasFocus;
2170  }
2171 
2172  if (w == mRuler)
2173  return RulerHasFocus;
2174 
2175  if (w == mTrackPanel) {
2176  return TrackPanelHasFocus;
2177  }
2178  // LIE if LyricsPanel window has focus.
2179  // we want to act as if TrackPanel has focus.
2180  if (w== mLyricsWindow) {
2181  return TrackPanelHasFocus;
2182  }
2183  if (w == mToolManager->GetBotDock()) {
2184  return BotDockHasFocus;
2185  }
2186 
2187  w = w->GetParent();
2188  }
2189 
2190  return AlwaysEnabledFlag;
2191 }
2192 
2194 {
2195  // This method determines all of the flags that determine whether
2196  // certain menu items and commands should be enabled or disabled,
2197  // and returns them in a bitfield. Note that if none of the flags
2198  // have changed, it's not necessary to even check for updates.
2199  auto flags = AlwaysEnabledFlag;
2200  // static variable, used to remember flags for next time.
2201  static auto lastFlags = flags;
2202 
2203  if (auto focus = wxWindow::FindFocus()) {
2204  while (focus && focus->GetParent())
2205  focus = focus->GetParent();
2206  if (focus && !static_cast<wxTopLevelWindow*>(focus)->IsIconized())
2207  flags |= NotMinimizedFlag;
2208  }
2209 
2210  // quick 'short-circuit' return.
2211  if ( checkActive && !IsActive() ){
2212  // short cirucit return should preserve flags that have not been calculated.
2213  flags = (lastFlags & ~NotMinimizedFlag) | flags;
2214  lastFlags = flags;
2215  return flags;
2216  }
2217 
2219  flags |= AudioIONotBusyFlag;
2220  else
2221  flags |= AudioIOBusyFlag;
2222 
2223  if( gAudioIO->IsPaused() )
2224  flags |= PausedFlag;
2225  else
2226  flags |= NotPausedFlag;
2227 
2229  flags |= TimeSelectedFlag;
2230 
2231  TrackListIterator iter(GetTracks());
2232  Track *t = iter.First();
2233  while (t) {
2234  flags |= TracksExistFlag;
2235  if (t->GetKind() == Track::Label) {
2236  LabelTrack *lt = (LabelTrack *) t;
2237 
2238  flags |= LabelTracksExistFlag;
2239 
2240  if (lt->GetSelected()) {
2241  flags |= TracksSelectedFlag;
2242  for (int i = 0; i < lt->GetNumLabels(); i++) {
2243  const LabelStruct *ls = lt->GetLabel(i);
2244  if (ls->getT0() >= mViewInfo.selectedRegion.t0() &&
2245  ls->getT1() <= mViewInfo.selectedRegion.t1()) {
2246  flags |= LabelsSelectedFlag;
2247  break;
2248  }
2249  }
2250  }
2251 
2252  if (lt->IsTextSelected()) {
2253  flags |= CutCopyAvailableFlag;
2254  }
2255  }
2256  else if (t->GetKind() == Track::Wave) {
2257  flags |= WaveTracksExistFlag;
2258  flags |= PlayableTracksExistFlag;
2259  if (t->GetSelected()) {
2260  flags |= TracksSelectedFlag;
2261  if (t->GetLinked()) {
2262  flags |= StereoRequiredFlag;
2263  }
2264  else {
2265  flags |= WaveTracksSelectedFlag;
2266  flags |= AudioTracksSelectedFlag;
2267  }
2268  }
2269  if( t->GetEndTime() > t->GetStartTime() )
2270  flags |= HasWaveDataFlag;
2271  }
2272 #if defined(USE_MIDI)
2273  else if (t->GetKind() == Track::Note) {
2274  NoteTrack *nt = (NoteTrack *) t;
2275 
2276  flags |= NoteTracksExistFlag;
2277 #ifdef EXPERIMENTAL_MIDI_OUT
2278  flags |= PlayableTracksExistFlag;
2279 #endif
2280 
2281  if (nt->GetSelected()) {
2282  flags |= TracksSelectedFlag;
2283  flags |= NoteTracksSelectedFlag;
2284  flags |= AudioTracksSelectedFlag; // even if not EXPERIMENTAL_MIDI_OUT
2285  }
2286  }
2287 #endif
2288  t = iter.Next();
2289  }
2290 
2291  if((msClipT1 - msClipT0) > 0.0)
2292  flags |= ClipboardFlag;
2293 
2294  if (GetUndoManager()->UnsavedChanges() || !IsProjectSaved())
2295  flags |= UnsavedChangesFlag;
2296 
2297  if (!mLastEffect.IsEmpty())
2298  flags |= HasLastEffectFlag;
2299 
2300  if (UndoAvailable())
2301  flags |= UndoAvailableFlag;
2302 
2303  if (RedoAvailable())
2304  flags |= RedoAvailableFlag;
2305 
2306  if (ZoomInAvailable() && (flags & TracksExistFlag))
2307  flags |= ZoomInAvailableFlag;
2308 
2309  if (ZoomOutAvailable() && (flags & TracksExistFlag))
2310  flags |= ZoomOutAvailableFlag;
2311 
2312  // TextClipFlag is currently unused (Jan 2017, 2.1.3 alpha)
2313  // and LabelTrack::IsTextClipSupported() is quite slow on Linux,
2314  // so disable for now (See bug 1575).
2315  // if ((flags & LabelTracksExistFlag) && LabelTrack::IsTextClipSupported())
2316  // flags |= TextClipFlag;
2317 
2318  flags |= GetFocusedFrame();
2319 
2320  double start, end;
2321  GetPlayRegion(&start, &end);
2322  if (IsPlayRegionLocked())
2323  flags |= PlayRegionLockedFlag;
2324  else if (start != end)
2325  flags |= PlayRegionNotLockedFlag;
2326 
2327  if (flags & AudioIONotBusyFlag) {
2328  if (flags & TimeSelectedFlag) {
2329  if (flags & TracksSelectedFlag) {
2330  flags |= CutCopyAvailableFlag;
2331  }
2332  }
2333  }
2334 
2335  if (wxGetApp().GetRecentFiles()->GetCount() > 0)
2336  flags |= HaveRecentFiles;
2337 
2338  if (IsSyncLocked())
2339  flags |= IsSyncLockedFlag;
2340  else
2341  flags |= IsNotSyncLockedFlag;
2342 
2343  if (!EffectManager::Get().RealtimeIsActive())
2344  flags |= IsRealtimeNotActiveFlag;
2345 
2346  if (!mIsCapturing)
2347  flags |= CaptureNotBusyFlag;
2348 
2350  if (bar->ControlToolBar::CanStopAudioStream())
2351  flags |= CanStopAudioStreamFlag;
2352 
2353  lastFlags = flags;
2354  return flags;
2355 }
2356 
2357 // Select the full time range, if no
2358 // time range is selected.
2360 {
2361  auto flags = GetUpdateFlags();
2362  if(!(flags & TracksSelectedFlag) ||
2364  OnSelectSomething(*this);
2365 }
2366 
2367 // Stop playing or recording, if paused.
2369 {
2370  auto flags = GetUpdateFlags();
2371  if( flags & PausedFlag )
2372  OnStop(*this);
2373 }
2374 
2376 {
2377  AProjectArray::iterator i;
2378  for (i = gAudacityProjects.begin(); i != gAudacityProjects.end(); ++i) {
2379  (*i)->ModifyToolbarMenus();
2380  }
2381 }
2382 
2384 {
2385  // Refreshes can occur during shutdown and the toolmanager may already
2386  // be deleted, so protect against it.
2387  if (!mToolManager) {
2388  return;
2389  }
2390 
2391  mCommandManager.Check(wxT("ShowScrubbingTB"),
2392  mToolManager->IsVisible(ScrubbingBarID));
2393  mCommandManager.Check(wxT("ShowDeviceTB"),
2394  mToolManager->IsVisible(DeviceBarID));
2395  mCommandManager.Check(wxT("ShowEditTB"),
2396  mToolManager->IsVisible(EditBarID));
2397  mCommandManager.Check(wxT("ShowMeterTB"),
2398  mToolManager->IsVisible(MeterBarID));
2399  mCommandManager.Check(wxT("ShowRecordMeterTB"),
2400  mToolManager->IsVisible(RecordMeterBarID));
2401  mCommandManager.Check(wxT("ShowPlayMeterTB"),
2402  mToolManager->IsVisible(PlayMeterBarID));
2403  mCommandManager.Check(wxT("ShowMixerTB"),
2404  mToolManager->IsVisible(MixerBarID));
2405  mCommandManager.Check(wxT("ShowSelectionTB"),
2406  mToolManager->IsVisible(SelectionBarID));
2407 #ifdef EXPERIMENTAL_SPECTRAL_EDITING
2408  mCommandManager.Check(wxT("ShowSpectralSelectionTB"),
2409  mToolManager->IsVisible(SpectralSelectionBarID));
2410 #endif
2411  mCommandManager.Check(wxT("ShowToolsTB"),
2412  mToolManager->IsVisible(ToolsBarID));
2413  mCommandManager.Check(wxT("ShowTranscriptionTB"),
2414  mToolManager->IsVisible(TranscriptionBarID));
2415  mCommandManager.Check(wxT("ShowTransportTB"),
2416  mToolManager->IsVisible(TransportBarID));
2417 
2418  // Now, go through each toolbar, and call EnableDisableButtons()
2419  for (int i = 0; i < ToolBarCount; i++) {
2420  mToolManager->GetToolBar(i)->EnableDisableButtons();
2421  }
2422 
2423  // These don't really belong here, but it's easier and especially so for
2424  // the Edit toolbar and the sync-lock menu item.
2425  bool active;
2426  gPrefs->Read(wxT("/AudioIO/SoundActivatedRecord"),&active, false);
2427  mCommandManager.Check(wxT("SoundActivation"), active);
2428 #ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT
2429  gPrefs->Read(wxT("/AudioIO/AutomatedInputLevelAdjustment"),&active, false);
2430  mCommandManager.Check(wxT("AutomatedInputLevelAdjustmentOnOff"), active);
2431 #endif
2432 
2434  mCommandManager.Check(wxT("PinnedHead"), active);
2435 
2436 #ifdef EXPERIMENTAL_DA
2437  gPrefs->Read(wxT("/AudioIO/Duplex"),&active, false);
2438 #else
2439  gPrefs->Read(wxT("/AudioIO/Duplex"),&active, true);
2440 #endif
2441  mCommandManager.Check(wxT("Duplex"), active);
2442  gPrefs->Read(wxT("/AudioIO/SWPlaythrough"),&active, false);
2443  mCommandManager.Check(wxT("SWPlaythrough"), active);
2444  gPrefs->Read(wxT("/GUI/SyncLockTracks"), &active, false);
2445  SetSyncLock(active);
2446  mCommandManager.Check(wxT("SyncLock"), active);
2447  gPrefs->Read(wxT("/GUI/TypeToCreateLabel"),&active, true);
2448  mCommandManager.Check(wxT("TypeToCreateLabel"), active);
2449 }
2450 
2451 // checkActive is a temporary hack that should be removed as soon as we
2452 // get multiple effect preview working
2453 void AudacityProject::UpdateMenus(bool checkActive)
2454 {
2455  //ANSWER-ME: Why UpdateMenus only does active project?
2456  //JKC: Is this test fixing a bug when multiple projects are open?
2457  //so that menu states work even when different in different projects?
2458  if (this != GetActiveProject())
2459  return;
2460 
2461  auto flags = GetUpdateFlags(checkActive);
2462  auto flags2 = flags;
2463 
2464  // We can enable some extra items if we have select-all-on-none.
2465  //EXPLAIN-ME: Why is this here rather than in GetUpdateFlags()?
2466  //ANSWER: Because flags2 is used in the menu enable/disable.
2467  //The effect still needs flags to determine whether it will need
2468  //to actually do the 'select all' to make the command valid.
2469  if (mWhatIfNoSelection != 0)
2470  {
2471  if ((flags & TracksExistFlag))
2472  {
2473  flags2 |= TracksSelectedFlag;
2474  if ((flags & WaveTracksExistFlag))
2475  {
2476  flags2 |= TimeSelectedFlag
2479  }
2480  }
2481  }
2482 
2483  if( mStopIfWasPaused )
2484  {
2485  if( flags & PausedFlag ){
2486  flags2 |= AudioIONotBusyFlag;
2487  }
2488  }
2489 
2490  // Return from this function if nothing's changed since
2491  // the last time we were here.
2492  if (flags == mLastFlags)
2493  return;
2494  mLastFlags = flags;
2495 
2497 
2498  // With select-all-on-none, some items that we don't want enabled may have
2499  // been enabled, since we changed the flags. Here we manually disable them.
2500  // 0 is grey out, 1 is Autoselect, 2 is Give warnings.
2501  if (mWhatIfNoSelection != 0)
2502  {
2503  if (!(flags & TimeSelectedFlag) | !(flags & TracksSelectedFlag))
2504  {
2505  mCommandManager.Enable(wxT("SplitCut"), false);
2506  mCommandManager.Enable(wxT("SplitDelete"), false);
2507  }
2508  if (!(flags & WaveTracksSelectedFlag))
2509  {
2510  mCommandManager.Enable(wxT("Split"), false);
2511  }
2512  if (!(flags & TimeSelectedFlag) | !(flags & WaveTracksSelectedFlag))
2513  {
2514  mCommandManager.Enable(wxT("ExportSel"), false);
2515  mCommandManager.Enable(wxT("SplitNew"), false);
2516  }
2517  if (!(flags & TimeSelectedFlag) | !(flags & AudioTracksSelectedFlag))
2518  {
2519  mCommandManager.Enable(wxT("Trim"), false);
2520  }
2521  }
2522 
2523 #if 0
2524  if (flags & CutCopyAvailableFlag) {
2525  mCommandManager.Enable(wxT("Copy"), true);
2526  mCommandManager.Enable(wxT("Cut"), true);
2527  }
2528 #endif
2529 
2531 }
2532 
2533 //
2534 // Tool selection commands
2535 //
2536 
2539 {
2540  ToolsToolBar *toolbar = GetToolsToolBar();
2541  if (toolbar) {
2542  toolbar->SetCurrentTool(tool);
2543  mTrackPanel->Refresh(false);
2544  }
2545 }
2546 
2548 void AudacityProject::OnSelectTool(const CommandContext &WXUNUSED(context) )
2549 {
2551 }
2552 
2554 void AudacityProject::OnZoomTool(const CommandContext &WXUNUSED(context) )
2555 {
2556  SetTool(zoomTool);
2557 }
2558 
2560 void AudacityProject::OnEnvelopeTool(const CommandContext &WXUNUSED(context) )
2561 {
2563 }
2564 
2566 void AudacityProject::OnTimeShiftTool(const CommandContext &WXUNUSED(context) )
2567 {
2568  SetTool(slideTool);
2569 }
2570 
2571 void AudacityProject::OnDrawTool(const CommandContext &WXUNUSED(context) )
2572 {
2573  SetTool(drawTool);
2574 }
2575 
2576 void AudacityProject::OnMultiTool(const CommandContext &WXUNUSED(context) )
2577 {
2578  SetTool(multiTool);
2579 }
2580 
2581 
2582 void AudacityProject::OnNextTool(const CommandContext &WXUNUSED(context) )
2583 {
2584  ToolsToolBar *toolbar = GetToolsToolBar();
2585  if (toolbar) {
2586  // Use GetDownTool() here since GetCurrentTool() can return a value that
2587  // doesn't represent the real tool if the Multi-tool is being used.
2588  toolbar->SetCurrentTool((toolbar->GetDownTool()+1)%numTools);
2589  mTrackPanel->Refresh(false);
2590  }
2591 }
2592 
2593 void AudacityProject::OnPrevTool(const CommandContext &WXUNUSED(context) )
2594 {
2595  ToolsToolBar *toolbar = GetToolsToolBar();
2596  if (toolbar) {
2597  // Use GetDownTool() here since GetCurrentTool() can return a value that
2598  // doesn't represent the real tool if the Multi-tool is being used.
2599  toolbar->SetCurrentTool((toolbar->GetDownTool()+(numTools-1))%numTools);
2600  mTrackPanel->Refresh(false);
2601  }
2602 }
2603 
2604 
2605 //
2606 // Audio I/O Commands
2607 //
2608 
2609 // TODO: Should all these functions which involve
2610 // the toolbar actually move into ControlToolBar?
2611 
2616 bool AudacityProject::MakeReadyToPlay(bool loop, bool cutpreview)
2617 {
2618  ControlToolBar *toolbar = GetControlToolBar();
2619  wxCommandEvent evt;
2620 
2621  // If this project is playing, stop playing
2623  toolbar->SetPlay(false); //Pops
2624  toolbar->SetStop(true); //Pushes stop down
2625  toolbar->OnStop(evt);
2626 
2627  ::wxMilliSleep(100);
2628  }
2629 
2630  // If it didn't stop playing quickly, or if some other
2631  // project is playing, return
2632  if (gAudioIO->IsBusy())
2633  return false;
2634 
2635  ControlToolBar::PlayAppearance appearance =
2639  toolbar->SetPlay(true, appearance);
2640  toolbar->SetStop(false);
2641 
2642  return true;
2643 }
2644 
2645 void AudacityProject::OnPlayOneSecond(const CommandContext &WXUNUSED(context) )
2646 {
2647  if( !MakeReadyToPlay() )
2648  return;
2649 
2650  double pos = mTrackPanel->GetMostRecentXPos();
2652  (SelectedRegion(pos - 0.5, pos + 0.5), GetDefaultPlayOptions(),
2654 }
2655 
2656 
2665 {
2666  if( !MakeReadyToPlay() )
2667  return;
2668 
2669  double pos = mTrackPanel->GetMostRecentXPos();
2670 
2671  double t0,t1;
2672  // check region between pointer and the nearest selection edge
2673  if (fabs(pos - mViewInfo.selectedRegion.t0()) <
2674  fabs(pos - mViewInfo.selectedRegion.t1())) {
2675  t0 = t1 = mViewInfo.selectedRegion.t0();
2676  } else {
2677  t0 = t1 = mViewInfo.selectedRegion.t1();
2678  }
2679  if( pos < t1)
2680  t0=pos;
2681  else
2682  t1=pos;
2683 
2684  // JKC: oneSecondPlay mode disables auto scrolling
2685  // On balance I think we should always do this in this function
2686  // since you are typically interested in the sound EXACTLY
2687  // where the cursor is.
2688  // TODO: have 'playing attributes' such as 'with_autoscroll'
2689  // rather than modes, since that's how we're now using the modes.
2690 
2691  // An alternative, commented out below, is to disable autoscroll
2692  // only when playing a short region, less than or equal to a second.
2693 // mLastPlayMode = ((t1-t0) > 1.0) ? normalPlay : oneSecondPlay;
2694 
2697 }
2698 
2699 // The next 4 functions provide a limited version of the
2700 // functionality of OnPlayToSelection() for keyboard users
2701 
2703 {
2704  if( !MakeReadyToPlay() )
2705  return;
2706 
2707  double t0 = mViewInfo.selectedRegion.t0();
2708  double beforeLen;
2709  gPrefs->Read(wxT("/AudioIO/CutPreviewBeforeLen"), &beforeLen, 2.0);
2710 
2712 }
2713 
2715 {
2716  if( !MakeReadyToPlay() )
2717  return;
2718 
2719  double t0 = mViewInfo.selectedRegion.t0();
2720  double t1 = mViewInfo.selectedRegion.t1();
2721  double afterLen;
2722  gPrefs->Read(wxT("/AudioIO/CutPreviewAfterLen"), &afterLen, 1.0);
2723 
2724  if ( t1 - t0 > 0.0 && t1 - t0 < afterLen )
2727  else
2729 }
2730 
2732 {
2733  if( !MakeReadyToPlay() )
2734  return;
2735 
2736  double t0 = mViewInfo.selectedRegion.t0();
2737  double t1 = mViewInfo.selectedRegion.t1();
2738  double beforeLen;
2739  gPrefs->Read(wxT("/AudioIO/CutPreviewBeforeLen"), &beforeLen, 2.0);
2740 
2741  if ( t1 - t0 > 0.0 && t1 - t0 < beforeLen )
2744  else
2746 }
2747 
2748 
2750 {
2751  if( !MakeReadyToPlay() )
2752  return;
2753 
2754  double t1 = mViewInfo.selectedRegion.t1();
2755  double afterLen;
2756  gPrefs->Read(wxT("/AudioIO/CutPreviewAfterLen"), &afterLen, 1.0);
2757 
2759 }
2760 
2762 {
2763  if (!MakeReadyToPlay())
2764  return;
2765 
2766  double t0 = mViewInfo.selectedRegion.t0();
2767  double t1 = mViewInfo.selectedRegion.t1();
2768  double beforeLen;
2769  gPrefs->Read(wxT("/AudioIO/CutPreviewBeforeLen"), &beforeLen, 2.0);
2770  double afterLen;
2771  gPrefs->Read(wxT("/AudioIO/CutPreviewAfterLen"), &afterLen, 1.0);
2772 
2773  if ( t1 - t0 > 0.0 && t1 - t0 < afterLen )
2775  else
2777 }
2778 
2780 {
2781  if (!MakeReadyToPlay())
2782  return;
2783 
2784  double t0 = mViewInfo.selectedRegion.t0();
2785  double t1 = mViewInfo.selectedRegion.t1();
2786  double beforeLen;
2787  gPrefs->Read(wxT("/AudioIO/CutPreviewBeforeLen"), &beforeLen, 2.0);
2788  double afterLen;
2789  gPrefs->Read(wxT("/AudioIO/CutPreviewAfterLen"), &afterLen, 1.0);
2790 
2791  if ( t1 - t0 > 0.0 && t1 - t0 < beforeLen )
2793  else
2795 }
2796 
2797 
2798 void AudacityProject::OnPlayLooped(const CommandContext &WXUNUSED(context) )
2799 {
2800  if( !MakeReadyToPlay(true) )
2801  return;
2802 
2803  // Now play in a loop
2804  // Will automatically set mLastPlayMode
2806 }
2807 
2808 void AudacityProject::OnPlayCutPreview(const CommandContext &WXUNUSED(context) )
2809 {
2810  if ( !MakeReadyToPlay(false, true) )
2811  return;
2812 
2813  // Play with cut preview
2814  GetControlToolBar()->PlayCurrentRegion(false, true);
2815 }
2816 
2817 void AudacityProject::OnPlayStop(const CommandContext &WXUNUSED(context) )
2818 {
2819  ControlToolBar *toolbar = GetControlToolBar();
2820 
2821  //If this project is playing, stop playing, make sure everything is unpaused.
2823  toolbar->SetPlay(false); //Pops
2824  toolbar->SetStop(true); //Pushes stop down
2825  toolbar->StopPlaying();
2826  }
2827  else if (gAudioIO->IsStreamActive()) {
2828  //If this project isn't playing, but another one is, stop playing the old and start the NEW.
2829 
2830  //find out which project we need;
2831  AudacityProject* otherProject = NULL;
2832  for(unsigned i=0; i<gAudacityProjects.size(); i++) {
2833  if(gAudioIO->IsStreamActive(gAudacityProjects[i]->GetAudioIOToken())) {
2834  otherProject=gAudacityProjects[i].get();
2835  break;
2836  }
2837  }
2838 
2839  //stop playing the other project
2840  if(otherProject) {
2841  ControlToolBar *otherToolbar = otherProject->GetControlToolBar();
2842  otherToolbar->SetPlay(false); //Pops
2843  otherToolbar->SetStop(true); //Pushes stop down
2844  otherToolbar->StopPlaying();
2845  }
2846 
2847  //play the front project
2848  if (!gAudioIO->IsBusy()) {
2849  //update the playing area
2851  //Otherwise, start playing (assuming audio I/O isn't busy)
2852  //toolbar->SetPlay(true); // Not needed as done in PlayPlayRegion.
2853  toolbar->SetStop(false);
2854 
2855  // Will automatically set mLastPlayMode
2856  toolbar->PlayCurrentRegion(false);
2857  }
2858  }
2859  else if (!gAudioIO->IsBusy()) {
2860  //Otherwise, start playing (assuming audio I/O isn't busy)
2861  //toolbar->SetPlay(true); // Not needed as done in PlayPlayRegion.
2862  toolbar->SetStop(false);
2863 
2864  // Will automatically set mLastPlayMode
2865  toolbar->PlayCurrentRegion(false);
2866  }
2867 }
2868 
2869 void AudacityProject::OnStop(const CommandContext &WXUNUSED(context) )
2870 {
2871  wxCommandEvent evt;
2872 
2873  GetControlToolBar()->OnStop(evt);
2874 }
2875 
2876 void AudacityProject::OnPause(const CommandContext &WXUNUSED(context) )
2877 {
2878  wxCommandEvent evt;
2879 
2880  GetControlToolBar()->OnPause(evt);
2881 }
2882 
2883 void AudacityProject::OnRecord(const CommandContext &WXUNUSED(context) )
2884 {
2885  wxCommandEvent evt;
2886  evt.SetInt(2); // 0 is default, use 1 to set shift on, 2 to clear it
2887 
2888  GetControlToolBar()->OnRecord(evt);
2889 }
2890 
2891 // If first choice is record same track 2nd choice is record NEW track
2892 // and vice versa.
2894 {
2895  wxCommandEvent evt;
2896  evt.SetInt(1); // 0 is default, use 1 to set shift on, 2 to clear it
2897 
2898  GetControlToolBar()->OnRecord(evt);
2899 }
2900 
2901 // The code for "OnPlayStopSelect" is simply the code of "OnPlayStop" and "OnStopSelect" merged.
2902 void AudacityProject::OnPlayStopSelect(const CommandContext &WXUNUSED(context) )
2903 {
2904  ControlToolBar *toolbar = GetControlToolBar();
2905  wxCommandEvent evt;
2906  if (DoPlayStopSelect(false, false))
2907  toolbar->OnStop(evt);
2908  else if (!gAudioIO->IsBusy()) {
2909  //Otherwise, start playing (assuming audio I/O isn't busy)
2910  //toolbar->SetPlay(true); // Not needed as set in PlayPlayRegion()
2911  toolbar->SetStop(false);
2912 
2913  // Will automatically set mLastPlayMode
2914  toolbar->PlayCurrentRegion(false);
2915  }
2916 }
2917 
2918 bool AudacityProject::DoPlayStopSelect(bool click, bool shift)
2919 {
2920  ControlToolBar *toolbar = GetControlToolBar();
2921 
2922  //If busy, stop playing, make sure everything is unpaused.
2923  if (GetScrubber().HasStartedScrubbing() ||
2925  toolbar->SetPlay(false); //Pops
2926  toolbar->SetStop(true); //Pushes stop down
2927 
2928  // change the selection
2929  auto time = gAudioIO->GetStreamTime();
2930  auto &selection = mViewInfo.selectedRegion;
2931  if (shift && click) {
2932  // Change the region selection, as if by shift-click at the play head
2933  auto t0 = selection.t0(), t1 = selection.t1();
2934  if (time < t0)
2935  // Grow selection
2936  t0 = time;
2937  else if (time > t1)
2938  // Grow selection
2939  t1 = time;
2940  else {
2941  // Shrink selection, changing the nearer boundary
2942  if (fabs(t0 - time) < fabs(t1 - time))
2943  t0 = time;
2944  else
2945  t1 = time;
2946  }
2947  selection.setTimes(t0, t1);
2948  }
2949  else if (click){
2950  // avoid a point at negative time.
2951  time = wxMax( time, 0 );
2952  // Set a point selection, as if by a click at the play head
2953  selection.setTimes(time, time);
2954  } else
2955  // How stop and set cursor always worked
2956  // -- change t0, collapsing to point only if t1 was greater
2957  selection.setT0(time, false);
2958 
2959  ModifyState(false); // without bWantsAutoSave
2960  return true;
2961  }
2962  return false;
2963 }
2964 
2965 void AudacityProject::OnStopSelect(const CommandContext &WXUNUSED(context) )
2966 {
2967  wxCommandEvent evt;
2968 
2969  if (gAudioIO->IsStreamActive()) {
2971  GetControlToolBar()->OnStop(evt);
2972  ModifyState(false); // without bWantsAutoSave
2973  }
2974 }
2975 
2977 {
2978  bool pause;
2979  gPrefs->Read(wxT("/AudioIO/SoundActivatedRecord"), &pause, false);
2980  gPrefs->Write(wxT("/AudioIO/SoundActivatedRecord"), !pause);
2981  gPrefs->Flush();
2983 }
2984 
2986 {
2987  bool value = !TracksPrefs::GetPinnedHeadPreference();
2990 
2991  // Change what happens in case transport is in progress right now
2992  auto ctb = GetActiveProject()->GetControlToolBar();
2993  if (ctb)
2995 
2996  auto ruler = GetRulerPanel();
2997  if (ruler)
2998  // Update button image
2999  ruler->UpdateButtonStates();
3000 
3001  auto &scrubber = GetScrubber();
3002  if (scrubber.HasStartedScrubbing())
3003  scrubber.SetScrollScrubbing(value);
3004 }
3005 
3007 {
3008  bool Duplex;
3009 #ifdef EXPERIMENTAL_DA
3010  gPrefs->Read(wxT("/AudioIO/Duplex"), &Duplex, false);
3011 #else
3012  gPrefs->Read(wxT("/AudioIO/Duplex"), &Duplex, true);
3013 #endif
3014  gPrefs->Write(wxT("/AudioIO/Duplex"), !Duplex);
3015  gPrefs->Flush();
3017 }
3018 
3020 {
3021  bool SWPlaythrough;
3022  gPrefs->Read(wxT("/AudioIO/SWPlaythrough"), &SWPlaythrough, false);
3023  gPrefs->Write(wxT("/AudioIO/SWPlaythrough"), !SWPlaythrough);
3024  gPrefs->Flush();
3026 }
3027 
3028 #ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT
3029 void AudacityProject::OnToggleAutomatedInputLevelAdjustment()
3030 {
3031  bool AVEnabled;
3032  gPrefs->Read(wxT("/AudioIO/AutomatedInputLevelAdjustment"), &AVEnabled, false);
3033  gPrefs->Write(wxT("/AudioIO/AutomatedInputLevelAdjustment"), !AVEnabled);
3034  gPrefs->Flush();
3036 }
3037 #endif
3038 
3040 {
3041  double stime = 0.0;
3042 
3043  if (t->GetKind() == Track::Wave) {
3044  WaveTrack *w = (WaveTrack *)t;
3045  stime = w->GetEndTime();
3046 
3047  WaveClip *c;
3048  int ndx;
3049  for (ndx = 0; ndx < w->GetNumClips(); ndx++) {
3050  c = w->GetClipByIndex(ndx);
3051  if (c->GetNumSamples() == 0)
3052  continue;
3053  if (c->GetStartTime() < stime) {
3054  stime = c->GetStartTime();
3055  }
3056  }
3057  }
3058  else if (t->GetKind() == Track::Label) {
3059  LabelTrack *l = (LabelTrack *)t;
3060  stime = l->GetStartTime();
3061  }
3062 
3063  return stime;
3064 }
3065 
3066 //sort based on flags. see Project.h for sort flags
3068 {
3069  size_t ndx = 0;
3070  int cmpValue;
3071  // This one place outside of TrackList where we must use undisguised
3072  // std::list iterators! Avoid this elsewhere!
3073  std::vector<TrackNodePointer> arr;
3074  arr.reserve(mTracks->size());
3075  bool lastTrackLinked = false;
3076  //sort by linked tracks. Assumes linked track follows owner in list.
3077 
3078  // First find the permutation.
3079  for (auto iter = mTracks->ListOfTracks::begin(),
3080  end = mTracks->ListOfTracks::end(); iter != end; ++iter) {
3081  const auto &track = *iter;
3082  if(lastTrackLinked) {
3083  //insert after the last track since this track should be linked to it.
3084  ndx++;
3085  }
3086  else {
3087  bool bArrayTrackLinked = false;
3088  for (ndx = 0; ndx < arr.size(); ++ndx) {
3089  Track &arrTrack = **arr[ndx].first;
3090  // Don't insert between channels of a stereo track!
3091  if( bArrayTrackLinked ){
3092  bArrayTrackLinked = false;
3093  }
3094  else if(flags & kAudacitySortByName) {
3095  //do case insensitive sort - cmpNoCase returns less than zero if the string is 'less than' its argument
3096  //also if we have case insensitive equality, then we need to sort by case as well
3097  //We sort 'b' before 'B' accordingly. We uncharacteristically use greater than for the case sensitive
3098  //compare because 'b' is greater than 'B' in ascii.
3099  cmpValue = track->GetName().CmpNoCase(arrTrack.GetName());
3100  if (cmpValue < 0 ||
3101  (0 == cmpValue && track->GetName().CompareTo(arrTrack.GetName()) > 0) )
3102  break;
3103  }
3104  //sort by time otherwise
3105  else if(flags & kAudacitySortByTime) {
3106  //we have to search each track and all its linked ones to fine the minimum start time.
3107  double time1, time2, tempTime;
3108  const Track* tempTrack;
3109  size_t candidatesLookedAt;
3110 
3111  candidatesLookedAt = 0;
3112  tempTrack = &*track;
3113  time1 = time2 = std::numeric_limits<double>::max(); //TODO: find max time value. (I don't think we have one yet)
3114  while(tempTrack) {
3115  tempTime = GetTime(tempTrack);
3116  time1 = std::min(time1, tempTime);
3117  if(tempTrack->GetLinked())
3118  tempTrack = tempTrack->GetLink();
3119  else
3120  tempTrack = NULL;
3121  }
3122 
3123  //get candidate's (from sorted array) time
3124  tempTrack = &arrTrack;
3125  while(tempTrack) {
3126  tempTime = GetTime(tempTrack);
3127  time2 = std::min(time2, tempTime);
3128  if(tempTrack->GetLinked() && (ndx+candidatesLookedAt < arr.size()-1) ) {
3129  candidatesLookedAt++;
3130  tempTrack = &**arr[ndx+candidatesLookedAt].first;
3131  }
3132  else
3133  tempTrack = NULL;
3134  }
3135 
3136  if (time1 < time2)
3137  break;
3138 
3139  ndx+=candidatesLookedAt;
3140  }
3141  bArrayTrackLinked = arrTrack.GetLinked();
3142  }
3143  }
3144  arr.insert(arr.begin() + ndx, TrackNodePointer{iter, mTracks.get()});
3145 
3146  lastTrackLinked = track->GetLinked();
3147  }
3148 
3149  // Now apply the permutation
3150  mTracks->Permute(arr);
3151 }
3152 
3153 void AudacityProject::OnSortTime(const CommandContext &WXUNUSED(context) )
3154 {
3156 
3157  PushState(_("Tracks sorted by time"), _("Sort by Time"));
3158 
3159  mTrackPanel->Refresh(false);
3160 }
3161 
3162 void AudacityProject::OnSortName(const CommandContext &WXUNUSED(context) )
3163 {
3165 
3166  PushState(_("Tracks sorted by name"), _("Sort by Name"));
3167 
3168  mTrackPanel->Refresh(false);
3169 }
3170 
3171 void AudacityProject::OnSkipStart(const CommandContext &WXUNUSED(context) )
3172 {
3173  wxCommandEvent evt;
3174 
3175  GetControlToolBar()->OnRewind(evt);
3176  ModifyState(false);
3177 }
3178 
3179 void AudacityProject::OnSkipEnd(const CommandContext &WXUNUSED(context) )
3180 {
3181  wxCommandEvent evt;
3182 
3183  GetControlToolBar()->OnFF(evt);
3184  ModifyState(false);
3185 }
3186 
3187 void AudacityProject::OnSeekLeftShort(const CommandContext &WXUNUSED(context) )
3188 {
3190 }
3191 
3192 void AudacityProject::OnSeekRightShort(const CommandContext &WXUNUSED(context) )
3193 {
3195 }
3196 
3197 void AudacityProject::OnSeekLeftLong(const CommandContext &WXUNUSED(context) )
3198 {
3200 }
3201 
3202 void AudacityProject::OnSeekRightLong(const CommandContext &WXUNUSED(context) )
3203 {
3205 }
3206 
3207 void AudacityProject::OnSelToStart(const CommandContext &WXUNUSED(context) )
3208 {
3209  Rewind(true);
3210  ModifyState(false);
3211 }
3212 
3213 void AudacityProject::OnSelToEnd(const CommandContext &WXUNUSED(context) )
3214 {
3215  SkipEnd(true);
3216  ModifyState(false);
3217 }
3218 
3220 {
3221  OnMoveToLabel(true);
3222 }
3223 
3225 {
3226  OnMoveToLabel(false);
3227 }
3228 
3230 {
3231  // Find the number of label tracks, and ptr to last track found
3232  Track* track = nullptr;
3233  int nLabelTrack = 0;
3235  for (Track* t = iter.First(); t; t = iter.Next()) {
3236  nLabelTrack++;
3237  track = t;
3238  }
3239 
3240  if (nLabelTrack == 0 ) {
3241  mTrackPanel->MessageForScreenReader(_("no label track"));
3242  }
3243  else if (nLabelTrack > 1) { // find first label track, if any, starting at the focused track
3244  track = mTrackPanel->GetFocusedTrack();
3245  while (track && track->GetKind() != Track::Label) {
3246  track = mTracks->GetNext(track, true);
3247  if (!track) {
3248  mTrackPanel->MessageForScreenReader(_("no label track at or below focused track"));
3249  }
3250  }
3251  }
3252 
3253  // If there is a single label track, or there is a label track at or below the focused track
3254  if (track) {
3255  LabelTrack* lt = static_cast<LabelTrack*>(track);
3256  int i;
3257  if (next)
3258  i = lt->FindNextLabel(GetSelection());
3259  else
3260  i = lt->FindPrevLabel(GetSelection());
3261 
3262  if (i >= 0) {
3263  const LabelStruct* label = lt->GetLabel(i);
3264  if (IsAudioActive()) {
3265  OnPlayStop(*this); // stop
3267  RedrawProject();
3268  OnPlayStop(*this); // play
3269  }
3270  else {
3272  mTrackPanel->ScrollIntoView(GetViewInfo().selectedRegion.t0());
3273  RedrawProject();
3274  }
3275 
3276  wxString message;
3277  message.Printf(wxT("%s %d of %d"), label->title, i + 1, lt->GetNumLabels() );
3279  }
3280  else {
3281  mTrackPanel->MessageForScreenReader(_("no labels in label track"));
3282  }
3283  }
3284 }
3285 
3289 
3292 {
3293  TrackListIterator iter( GetTracks() );
3295  if( t == NULL ) // if there isn't one, focus on last
3296  {
3297  t = iter.Last();
3299  mTrackPanel->EnsureVisible( t );
3300  ModifyState(false);
3301  return;
3302  }
3303 
3304  Track* p = NULL;
3305  bool tSelected = false;
3306  bool pSelected = false;
3307  if( shift )
3308  {
3309  p = mTracks->GetPrev( t, true ); // Get previous track
3310  if( p == NULL ) // On first track
3311  {
3312  // JKC: wxBell() is probably for accessibility, so a blind
3313  // user knows they were at the top track.
3314  wxBell();
3316  {
3317  TrackListIterator iter( GetTracks() );
3318  p = iter.Last();
3319  }
3320  else
3321  {
3322  mTrackPanel->EnsureVisible( t );
3323  return;
3324  }
3325  }
3326  tSelected = t->GetSelected();
3327  if (p)
3328  pSelected = p->GetSelected();
3329  if( tSelected && pSelected )
3330  {
3332  ( *mTracks, *t, false, false, GetMixerBoard() );
3333  mTrackPanel->SetFocusedTrack( p ); // move focus to next track down
3334  mTrackPanel->EnsureVisible( p );
3335  ModifyState(false);
3336  return;
3337  }
3338  if( tSelected && !pSelected )
3339  {
3341  ( *mTracks, *p, true, false, GetMixerBoard() );
3342  mTrackPanel->SetFocusedTrack( p ); // move focus to next track down
3343  mTrackPanel->EnsureVisible( p );
3344  ModifyState(false);
3345  return;
3346  }
3347  if( !tSelected && pSelected )
3348  {
3350  ( *mTracks, *p, false, false, GetMixerBoard() );
3351  mTrackPanel->SetFocusedTrack( p ); // move focus to next track down
3352  mTrackPanel->EnsureVisible( p );
3353  ModifyState(false);
3354  return;
3355  }
3356  if( !tSelected && !pSelected )
3357  {
3359  ( *mTracks, *t, true, false, GetMixerBoard() );
3360  mTrackPanel->SetFocusedTrack( p ); // move focus to next track down
3361  mTrackPanel->EnsureVisible( p );
3362  ModifyState(false);
3363  return;
3364  }
3365  }
3366  else
3367  {
3368  p = mTracks->GetPrev( t, true ); // Get next track
3369  if( p == NULL ) // On last track so stay there?
3370  {
3371  wxBell();
3373  {
3374  TrackListIterator iter( GetTracks() );
3375  for( Track *d = iter.First(); d; d = iter.Next( true ) )
3376  {
3377  p = d;
3378  }
3379  mTrackPanel->SetFocusedTrack( p ); // Wrap to the first track
3380  mTrackPanel->EnsureVisible( p );
3381  ModifyState(false);
3382  return;
3383  }
3384  else
3385  {
3386  mTrackPanel->EnsureVisible( t );
3387  return;
3388  }
3389  }
3390  else
3391  {
3392  mTrackPanel->SetFocusedTrack( p ); // move focus to next track down
3393  mTrackPanel->EnsureVisible( p );
3394  ModifyState(false);
3395  return;
3396  }
3397  }
3398 }
3399 
3404 {
3405  Track *t;
3406  Track *n;
3407  TrackListIterator iter( GetTracks() );
3408  bool tSelected,nSelected;
3409 
3410  t = mTrackPanel->GetFocusedTrack(); // Get currently focused track
3411  if( t == NULL ) // if there isn't one, focus on first
3412  {
3413  t = iter.First();
3415  mTrackPanel->EnsureVisible( t );
3416  ModifyState(false);
3417  return;
3418  }
3419 
3420  if( shift )
3421  {
3422  n = mTracks->GetNext( t, true ); // Get next track
3423  if( n == NULL ) // On last track so stay there
3424  {
3425  wxBell();
3427  {
3428  TrackListIterator iter( GetTracks() );
3429  n = iter.First();
3430  }
3431  else
3432  {
3433  mTrackPanel->EnsureVisible( t );
3434  return;
3435  }
3436  }
3437  tSelected = t->GetSelected();
3438  nSelected = n->GetSelected();
3439  if( tSelected && nSelected )
3440  {
3442  ( *mTracks, *t, false, false, GetMixerBoard() );
3443  mTrackPanel->SetFocusedTrack( n ); // move focus to next track down
3444  mTrackPanel->EnsureVisible( n );
3445  ModifyState(false);
3446  return;
3447  }
3448  if( tSelected && !nSelected )
3449  {
3451  ( *mTracks, *n, true, false, GetMixerBoard() );
3452  mTrackPanel->SetFocusedTrack( n ); // move focus to next track down
3453  mTrackPanel->EnsureVisible( n );
3454  ModifyState(false);
3455  return;
3456  }
3457  if( !tSelected && nSelected )
3458  {
3460  ( *mTracks, *n, false, false, GetMixerBoard() );
3461  mTrackPanel->SetFocusedTrack( n ); // move focus to next track down
3462  mTrackPanel->EnsureVisible( n );
3463  ModifyState(false);
3464  return;
3465  }
3466  if( !tSelected && !nSelected )
3467  {
3469  ( *mTracks, *t, true, false, GetMixerBoard() );
3470  mTrackPanel->SetFocusedTrack( n ); // move focus to next track down
3471  mTrackPanel->EnsureVisible( n );
3472  ModifyState(false);
3473  return;
3474  }
3475  }
3476  else
3477  {
3478  n = mTracks->GetNext( t, true ); // Get next track
3479  if( n == NULL ) // On last track so stay there
3480  {
3481  wxBell();
3483  {
3484  TrackListIterator iter( GetTracks() );
3485  n = iter.First();
3486  mTrackPanel->SetFocusedTrack( n ); // Wrap to the first track
3487  mTrackPanel->EnsureVisible( n );
3488  ModifyState(false);
3489  return;
3490  }
3491  else
3492  {
3493  mTrackPanel->EnsureVisible( t );
3494  return;
3495  }
3496  }
3497  else
3498  {
3499  mTrackPanel->SetFocusedTrack( n ); // move focus to next track down
3500  mTrackPanel->EnsureVisible( n );
3501  ModifyState(false);
3502  return;
3503  }
3504  }
3505 }
3506 
3507 void AudacityProject::OnCursorUp(const CommandContext &WXUNUSED(context) )
3508 {
3509  OnPrevTrack( false );
3510 }
3511 
3512 void AudacityProject::OnCursorDown(const CommandContext &WXUNUSED(context) )
3513 {
3514  OnNextTrack( false );
3515 }
3516 
3517 void AudacityProject::OnFirstTrack(const CommandContext &WXUNUSED(context) )
3518 {
3520  if (!t)
3521  return;
3522 
3523  TrackListIterator iter(GetTracks());
3524  Track *f = iter.First();
3525  if (t != f)
3526  {
3528  ModifyState(false);
3529  }
3531 }
3532 
3533 void AudacityProject::OnLastTrack(const CommandContext &WXUNUSED(context) )
3534 {
3536  if (!t)
3537  return;
3538 
3539  TrackListIterator iter(GetTracks());
3540  Track *l = iter.Last();
3541  if (t != l)
3542  {
3544  ModifyState(false);
3545  }
3547 }
3548 
3549 void AudacityProject::OnShiftUp(const CommandContext &WXUNUSED(context) )
3550 {
3551  OnPrevTrack( true );
3552 }
3553 
3554 void AudacityProject::OnShiftDown(const CommandContext &WXUNUSED(context) )
3555 {
3556  OnNextTrack( true );
3557 }
3558 
3559 #include "TrackPanelAx.h"
3560 void AudacityProject::OnToggle(const CommandContext &WXUNUSED(context) )
3561 {
3562  Track *t;
3563 
3564  t = mTrackPanel->GetFocusedTrack(); // Get currently focused track
3565  if (!t)
3566  return;
3567 
3569  ( *mTracks, *t, !t->GetSelected(), true, GetMixerBoard() );
3570  mTrackPanel->EnsureVisible( t );
3571  ModifyState(false);
3572 
3573  mTrackPanel->GetAx().Updated();
3574 
3575  return;
3576 }
3577 
3578 void AudacityProject::HandleListSelection(Track *t, bool shift, bool ctrl,
3579  bool modifyState)
3580 {
3582  ( *GetTracks(), mViewInfo, *t,
3583  shift, ctrl, IsSyncLocked(), GetMixerBoard() );
3584 
3585  if (! ctrl )
3587  Refresh(false);
3588  if (modifyState)
3589  ModifyState(true);
3590 }
3591 
3592 // If this returns true, then there was a key up, and nothing more to do,
3593 // after this function has completed.
3594 // (at most this function just does a ModifyState for the keyup)
3596 {
3597  auto evt = context.pEvt;
3598  bool bKeyUp = (evt) && evt->GetEventType() == wxEVT_KEY_UP;
3599 
3600  if( IsAudioActive() )
3601  return bKeyUp;
3602  if( !bKeyUp )
3603  return false;
3604 
3605  ModifyState(false);
3606  return true;
3607 }
3608 
3610 {
3611  if( !OnlyHandleKeyUp( context ) )
3613 }
3614 
3616 {
3617  if( !OnlyHandleKeyUp( context ) )
3619 }
3620 
3622 {
3624 }
3625 
3627 {
3629 }
3630 
3632 {
3633  OnCursorMove( -mSeekLong );
3634 }
3635 
3637 {
3639 }
3640 
3642 {
3644 }
3645 
3647 {
3649 }
3650 
3652 {
3653  if( !OnlyHandleKeyUp( context ) )
3655 }
3656 
3658 {
3659  if( !OnlyHandleKeyUp( context ) )
3661 }
3662 
3664 {
3665  if( !OnlyHandleKeyUp( context ) )
3667 }
3668 
3670 {
3671  if( !OnlyHandleKeyUp( context ) )
3673 }
3674 
3675 #include "tracks/ui/TimeShiftHandle.h"
3676 
3677 // This function returns the amount moved. Possibly 0.0.
3679  ( ViewInfo &viewInfo, Track *track,
3680  TrackList &trackList, bool syncLocked, bool right )
3681 {
3682  // just dealing with clips in wave tracks for the moment. Note tracks??
3683  if (track && track->GetKind() == Track::Wave) {
3684  ClipMoveState state;
3685 
3686  auto wt = static_cast<WaveTrack*>(track);
3687  auto t0 = viewInfo.selectedRegion.t0();
3688 
3689  state.capturedClip = wt->GetClipAtTime( t0 );
3690  if (state.capturedClip == nullptr && track->GetLinked() && track->GetLink()) {
3691  // the clips in the right channel may be different from the left
3692  track = track->GetLink();
3693  wt = static_cast<WaveTrack*>(track);
3694  state.capturedClip = wt->GetClipAtTime(t0);
3695  }
3696  if (state.capturedClip == nullptr)
3697  return 0.0;
3698 
3699  state.capturedClipIsSelection =
3700  track->GetSelected() && !viewInfo.selectedRegion.isPoint();
3701  state.trackExclusions.clear();
3702 
3704  ( state, viewInfo, *track, trackList, syncLocked, t0 );
3705 
3706  auto desiredT0 = viewInfo.OffsetTimeByPixels( t0, ( right ? 1 : -1 ) );
3707  auto desiredSlideAmount = desiredT0 - t0;
3708 
3709  // set it to a sample point, and minimum of 1 sample point
3710  if (!right)
3711  desiredSlideAmount *= -1;
3712  double nSamples = rint(wt->GetRate() * desiredSlideAmount);
3713  nSamples = std::max(nSamples, 1.0);
3714  desiredSlideAmount = nSamples / wt->GetRate();
3715  if (!right)
3716  desiredSlideAmount *= -1;
3717 
3718  state.hSlideAmount = desiredSlideAmount;
3719  TimeShiftHandle::DoSlideHorizontal( state, trackList, *track );
3720 
3721  // update t0 and t1. There is the possibility that the updated
3722  // t0 may no longer be within the clip due to rounding errors,
3723  // so t0 is adjusted so that it is.
3724  double newT0 = t0 + state.hSlideAmount;
3725  if (newT0 < state.capturedClip->GetStartTime())
3726  newT0 = state.capturedClip->GetStartTime();
3727  if (newT0 > state.capturedClip->GetEndTime())
3728  newT0 = state.capturedClip->GetEndTime();
3729  double diff = viewInfo.selectedRegion.duration();
3730  viewInfo.selectedRegion.setTimes(newT0, newT0 + diff);
3731 
3732  return state.hSlideAmount;
3733  }
3734  return 0.0;
3735 }
3736 
3737 void AudacityProject::DoClipLeftOrRight(bool right, bool keyUp )
3738 {
3739  if (keyUp) {
3741  return;
3742  }
3743 
3744  auto &panel = *GetTrackPanel();
3745 
3746  auto amount = OnClipMove
3747  ( mViewInfo, panel.GetFocusedTrack(),
3748  *GetTracks(), IsSyncLocked(), right );
3749 
3750  panel.ScrollIntoView(mViewInfo.selectedRegion.t0());
3751  panel.Refresh(false);
3752 
3753  if (amount != 0.0) {
3754  wxString message = right? _("Time shifted clips to the right") :
3755  _("Time shifted clips to the left");
3756 
3757  // The following use of the UndoPush flags is so that both a single
3758  // keypress (keydown, then keyup), and holding down a key
3759  // (multiple keydowns followed by a keyup) result in a single
3760  // entry in Audacity's history dialog.
3761  PushState(message, _("Time-Shift"), UndoPush::CONSOLIDATE);
3762  }
3763 
3764  if ( amount == 0.0 )
3765  panel.MessageForScreenReader( _("clip not moved"));
3766 }
3767 
3769 {
3770  auto evt = context.pEvt;
3771  if (evt)
3772  DoClipLeftOrRight( false, evt->GetEventType() == wxEVT_KEY_UP );
3773  else { // called from menu, so simulate keydown and keyup
3774  DoClipLeftOrRight( false, false );
3775  DoClipLeftOrRight( false, true );
3776  }
3777 }
3778 
3780 {
3781  auto evt = context.pEvt;
3782  if (evt)
3783  DoClipLeftOrRight( true, evt->GetEventType() == wxEVT_KEY_UP );
3784  else { // called from menu, so simulate keydown and keyup
3785  DoClipLeftOrRight( true, false );
3786  DoClipLeftOrRight( true, true );
3787  }
3788 }
3789 
3790 //this pops up a dialog which allows the left selection to be set.
3791 //If playing/recording is happening, it sets the left selection at
3792 //the current play position.
3794 {
3795  bool bSelChanged = false;
3797  {
3798  double indicator = gAudioIO->GetStreamTime();
3799  mViewInfo.selectedRegion.setT0(indicator, false);
3800  bSelChanged = true;
3801  }
3802  else
3803  {
3804  auto fmt = GetSelectionFormat();
3805  TimeDialog dlg(this, _("Set Left Selection Boundary"),
3806  fmt, mRate, mViewInfo.selectedRegion.t0(), _("Position"));
3807 
3808  if (wxID_OK == dlg.ShowModal())
3809  {
3810  //Get the value from the dialog
3812  std::max(0.0, dlg.GetTimeValue()), false);
3813  bSelChanged = true;
3814  }
3815  }
3816 
3817  if (bSelChanged)
3818  {
3819  ModifyState(false);
3820  mTrackPanel->Refresh(false);
3821  }
3822 }
3823 
3824 
3826 {
3827  bool bSelChanged = false;
3829  {
3830  double indicator = gAudioIO->GetStreamTime();
3831  mViewInfo.selectedRegion.setT1(indicator, false);
3832  bSelChanged = true;
3833  }
3834  else
3835  {
3836  auto fmt = GetSelectionFormat();
3837  TimeDialog dlg(this, _("Set Right Selection Boundary"),
3838  fmt, mRate, mViewInfo.selectedRegion.t1(), _("Position"));
3839 
3840  if (wxID_OK == dlg.ShowModal())
3841  {
3842  //Get the value from the dialog
3844  std::max(0.0, dlg.GetTimeValue()), false);
3845  bSelChanged = true;
3846  }
3847  }
3848 
3849  if (bSelChanged)
3850  {
3851  ModifyState(false);
3852  mTrackPanel->Refresh(false);
3853  }
3854 }
3855 
3857 {
3858  // Focus won't take in a dock unless at least one descendant window
3859  // accepts focus. Tell controls to take focus for the duration of this
3860  // function, only. Outside of this, they won't steal the focus when
3861  // clicked.
3862  auto temp1 = AButton::TemporarilyAllowFocus();
3863  auto temp2 = ASlider::TemporarilyAllowFocus();
3864  auto temp3 = MeterPanel::TemporarilyAllowFocus();
3865 
3866 
3867  // Define the set of windows we rotate among.
3868  static const unsigned rotationSize = 3u;
3869 
3870  wxWindow *const begin [rotationSize] = {
3871  GetTopPanel(),
3872  GetTrackPanel(),
3873  mToolManager->GetBotDock(),
3874  };
3875 
3876  const auto end = begin + rotationSize;
3877 
3878  // helper functions
3879  auto IndexOf = [&](wxWindow *pWindow) {
3880  return std::find(begin, end, pWindow) - begin;
3881  };
3882 
3883  auto FindAncestor = [&]() {
3884  wxWindow *pWindow = wxWindow::FindFocus();
3885  unsigned index = rotationSize;
3886  while ( pWindow &&
3887  (rotationSize == (index = IndexOf(pWindow) ) ) )
3888  pWindow = pWindow->GetParent();
3889  return index;
3890  };
3891 
3892  const auto idx = FindAncestor();
3893  if (idx == rotationSize)
3894  return;
3895 
3896  auto idx2 = idx;
3897  auto increment = (forward ? 1 : rotationSize - 1);
3898 
3899  while( idx != (idx2 = (idx2 + increment) % rotationSize) ) {
3900  wxWindow *toFocus = begin[idx2];
3901  bool bIsAnEmptyDock=false;
3902  if( idx2 != 1 )
3903  bIsAnEmptyDock = ((idx2==0)?mToolManager->GetTopDock() : mToolManager->GetBotDock())->
3904  GetChildren().GetCount() < 1;
3905 
3906  // Skip docks that are empty (Bug 1564).
3907  if( !bIsAnEmptyDock ){
3908  toFocus->SetFocus();
3909  if ( FindAncestor() == idx2 )
3910  // The focus took!
3911  break;
3912  }
3913  }
3914 }
3915 
3916 void AudacityProject::NextFrame(const CommandContext &WXUNUSED(context) )
3917 {
3918  NextOrPrevFrame(true);
3919 }
3920 
3921 void AudacityProject::PrevFrame(const CommandContext &WXUNUSED(context) )
3922 {
3923  NextOrPrevFrame(false);
3924 }
3925 
3926 void AudacityProject::NextWindow(const CommandContext &WXUNUSED(context) )
3927 {
3928  wxWindow *w = wxGetTopLevelParent(wxWindow::FindFocus());
3929  const auto & list = GetChildren();
3930  auto iter = list.begin(), end = list.end();
3931 
3932  // If the project window has the current focus, start the search with the first child
3933  if (w == this)
3934  {
3935  }
3936  // Otherwise start the search with the current window's next sibling
3937  else
3938  {
3939  // Find the window in this projects children. If the window with the
3940  // focus isn't a child of this project (like when a dialog is created
3941  // without specifying a parent), then we'll get back NULL here.
3942  while (iter != end && *iter != w)
3943  ++iter;
3944  if (iter != end)
3945  ++iter;
3946  }
3947 
3948  // Search for the next toplevel window
3949  for (; iter != end; ++iter)
3950  {
3951  // If it's a toplevel, visible (we have hidden windows) and is enabled,
3952  // then we're done. The IsEnabled() prevents us from moving away from
3953  // a modal dialog because all other toplevel windows will be disabled.
3954  w = *iter;
3955  if (w->IsTopLevel() && w->IsShown() && w->IsEnabled())
3956  {
3957  break;
3958  }
3959  }
3960 
3961  // Ran out of siblings, so make the current project active
3962  if ((iter == end) && IsEnabled())
3963  {
3964  w = this;
3965  }
3966 
3967  // And make sure it's on top (only for floating windows...project window will not raise)
3968  // (Really only works on Windows)
3969  w->Raise();
3970 
3971 
3972 #if defined(__WXMAC__) || defined(__WXGTK__)
3973  // bug 868
3974  // Simulate a TAB key press before continuing, else the cycle of
3975  // navigation among top level windows stops because the keystrokes don't
3976  // go to the CommandManager.
3977  if (dynamic_cast<wxDialog*>(w)) {
3978  w->SetFocus();
3979  }
3980 #endif
3981 }
3982 
3983 void AudacityProject::PrevWindow(const CommandContext &WXUNUSED(context) )
3984 {
3985  wxWindow *w = wxGetTopLevelParent(wxWindow::FindFocus());
3986  const auto & list = GetChildren();
3987  auto iter = list.rbegin(), end = list.rend();
3988 
3989  // If the project window has the current focus, start the search with the last child
3990  if (w == this)
3991  {
3992  }
3993  // Otherwise start the search with the current window's previous sibling
3994  else
3995  {
3996  while (iter != end && *iter != w)
3997  ++iter;
3998  if (iter != end)
3999  ++iter;
4000  }
4001 
4002  // Search for the previous toplevel window
4003  for (; iter != end; ++iter)
4004  {
4005  // If it's a toplevel and is visible (we have come hidden windows), then we're done
4006  w = *iter;
4007  if (w->IsTopLevel() && w->IsShown() && IsEnabled())
4008  {
4009  break;
4010  }
4011  }
4012 
4013  // Ran out of siblings, so make the current project active
4014  if ((iter == end) && IsEnabled())
4015  {
4016  w = this;
4017  }
4018 
4019  // And make sure it's on top (only for floating windows...project window will not raise)
4020  // (Really only works on Windows)
4021  w->Raise();
4022 
4023 
4024 #if defined(__WXMAC__) || defined(__WXGTK__)
4025  // bug 868
4026  // Simulate a TAB key press before continuing, else the cycle of
4027  // navigation among top level windows stops because the keystrokes don't
4028  // go to the CommandManager.
4029  if (dynamic_cast<wxDialog*>(w)) {
4030  w->SetFocus();
4031  }
4032 #endif
4033 }
4034 
4037 void AudacityProject::OnTrackPan(const CommandContext &WXUNUSED(context) )
4038 {
4039  Track *const track = mTrackPanel->GetFocusedTrack();
4040  if (!track || (track->GetKind() != Track::Wave)) {
4041  return;
4042  }
4043  const auto wt = static_cast<WaveTrack*>(track);
4044 
4045  LWSlider *slider = mTrackPanel->PanSlider(wt);
4046  if (slider->ShowDialog()) {
4047  SetTrackPan(wt, slider);
4048  }
4049 }
4050 
4051 void AudacityProject::OnTrackPanLeft(const CommandContext &WXUNUSED(context) )
4052 {
4053  Track *const track = mTrackPanel->GetFocusedTrack();
4054  if (!track || (track->GetKind() != Track::Wave)) {
4055  return;
4056  }
4057  const auto wt = static_cast<WaveTrack*>(track);
4058 
4059  LWSlider *slider = mTrackPanel->PanSlider(wt);
4060  slider->Decrease(1);
4061  SetTrackPan(wt, slider);
4062 }
4063 
4064 void AudacityProject::OnTrackPanRight(const CommandContext &WXUNUSED(context) )
4065 {
4066  Track *const track = mTrackPanel->GetFocusedTrack();
4067  if (!track || (track->GetKind() != Track::Wave)) {
4068  return;
4069  }
4070  const auto wt = static_cast<WaveTrack*>(track);
4071 
4072  LWSlider *slider = mTrackPanel->PanSlider(wt);
4073  slider->Increase(1);
4074  SetTrackPan(wt, slider);
4075 }
4076 
4077 void AudacityProject::OnTrackGain(const CommandContext &WXUNUSED(context) )
4078 {
4080  Track *const track = mTrackPanel->GetFocusedTrack();
4081  if (!track || (track->GetKind() != Track::Wave)) {
4082  return;
4083  }
4084  const auto wt = static_cast<WaveTrack*>(track);
4085 
4086  LWSlider *slider = mTrackPanel->GainSlider(wt);
4087  if (slider->ShowDialog()) {
4088  SetTrackGain(wt, slider);
4089  }
4090 }
4091 
4092 void AudacityProject::OnTrackGainInc(const CommandContext &WXUNUSED(context) )
4093 {
4094  Track *const track = mTrackPanel->GetFocusedTrack();
4095  if (!track || (track->GetKind() != Track::Wave)) {
4096  return;
4097  }
4098  const auto wt = static_cast<WaveTrack*>(track);
4099 
4100  LWSlider *slider = mTrackPanel->GainSlider(wt);
4101  slider->Increase(1);
4102  SetTrackGain(wt, slider);
4103 }
4104 
4105 void AudacityProject::OnTrackGainDec(const CommandContext &WXUNUSED(context) )
4106 {
4107  Track *const track = mTrackPanel->GetFocusedTrack();
4108  if (!track || (track->GetKind() != Track::Wave)) {
4109  return;
4110  }
4111  const auto wt = static_cast<WaveTrack*>(track);
4112 
4113  LWSlider *slider = mTrackPanel->GainSlider(wt);
4114  slider->Decrease(1);
4115  SetTrackGain(wt, slider);
4116 }
4117 
4118 void AudacityProject::OnTrackMenu(const CommandContext &WXUNUSED(context) )
4119 {
4121 }
4122 
4123 void AudacityProject::OnTrackMute(const CommandContext &WXUNUSED(context) )
4124 {
4125  Track *t = NULL;
4126  if (!t) {
4128  if (!dynamic_cast<PlayableTrack*>(t))
4129  return;
4130  }
4131  DoTrackMute(t, false);
4132 }
4133 
4134 void AudacityProject::OnTrackSolo(const CommandContext &WXUNUSED(context) )
4135 {
4136  Track *t = NULL;
4137  if (!t)
4138  {
4140  if (!dynamic_cast<PlayableTrack*>(t))
4141  return;
4142  }
4143  DoTrackSolo(t, false);
4144 }
4145 
4146 void AudacityProject::OnTrackClose(const CommandContext &WXUNUSED(context) )
4147 {
4149  if (!t)
4150  return;
4151 
4152  if (IsAudioActive())
4153  {
4154  this->TP_DisplayStatusMessage(_("Can't delete track with active audio"));
4155  wxBell();
4156  return;
4157  }
4158 
4159  RemoveTrack(t);
4160 
4162  GetTrackPanel()->Refresh(false);
4163 }
4164 
4165 void AudacityProject::OnTrackMoveUp(const CommandContext &WXUNUSED(context) )
4166 {
4167  Track *const focusedTrack = mTrackPanel->GetFocusedTrack();
4168  if (mTracks->CanMoveUp(focusedTrack)) {
4169  MoveTrack(focusedTrack, OnMoveUpID);
4170  mTrackPanel->Refresh(false);
4171  }
4172 }
4173 
4174 void AudacityProject::OnTrackMoveDown(const CommandContext &WXUNUSED(context) )
4175 {
4176  Track *const focusedTrack = mTrackPanel->GetFocusedTrack();
4177  if (mTracks->CanMoveDown(focusedTrack)) {
4178  MoveTrack(focusedTrack, OnMoveDownID);
4179  mTrackPanel->Refresh(false);
4180  }
4181 }
4182 
4183 void AudacityProject::OnTrackMoveTop(const CommandContext &WXUNUSED(context) )
4184 {
4185  Track *const focusedTrack = mTrackPanel->GetFocusedTrack();
4186  if (mTracks->CanMoveUp(focusedTrack)) {
4187  MoveTrack(focusedTrack, OnMoveTopID);
4188  mTrackPanel->Refresh(false);
4189  }
4190 }
4191 
4193 {
4194  Track *const focusedTrack = mTrackPanel->GetFocusedTrack();
4195  if (mTracks->CanMoveDown(focusedTrack)) {
4196  MoveTrack(focusedTrack, OnMoveBottomID);
4197  mTrackPanel->Refresh(false);
4198  }
4199 }
4200 
4202 
4204 {
4205  wxString longDesc, shortDesc;
4206 
4207  auto pt = dynamic_cast<PlayableTrack*>(target);
4208  switch (choice)
4209  {
4210  case OnMoveTopID:
4211  /* i18n-hint: Past tense of 'to move', as in 'moved audio track up'.*/
4212  longDesc = _("Moved '%s' to Top");
4213  shortDesc = _("Move Track to Top");
4214 
4215  while (mTracks->CanMoveUp(target)) {
4216  if (mTracks->Move(target, true)) {
4217  MixerBoard* pMixerBoard = this->GetMixerBoard(); // Update mixer board.
4218  if (pMixerBoard && pt)
4219  pMixerBoard->MoveTrackCluster(pt, true);
4220  }
4221  }
4222  break;
4223  case OnMoveBottomID:
4224  /* i18n-hint: Past tense of 'to move', as in 'moved audio track up'.*/
4225  longDesc = _("Moved '%s' to Bottom");
4226  shortDesc = _("Move Track to Bottom");
4227 
4228  while (mTracks->CanMoveDown(target)) {
4229  if (mTracks->Move(target, false)) {
4230  MixerBoard* pMixerBoard = this->GetMixerBoard(); // Update mixer board.
4231  if (pMixerBoard && pt)
4232  pMixerBoard->MoveTrackCluster(pt, false);
4233  }
4234  }
4235  break;
4236  default:
4237  bool bUp = (OnMoveUpID == choice);
4238 
4239  if (mTracks->Move(target, bUp)) {
4240  MixerBoard* pMixerBoard = this->GetMixerBoard();
4241  if (pMixerBoard && pt)
4242  pMixerBoard->MoveTrackCluster(pt, bUp);
4243  }
4244  longDesc =
4245  /* i18n-hint: Past tense of 'to move', as in 'moved audio track up'.*/
4246  bUp? _("Moved '%s' Up")
4247  : _("Moved '%s' Down");
4248  shortDesc =
4249  /* i18n-hint: Past tense of 'to move', as in 'moved audio track up'.*/
4250  bUp? _("Move Track Up")
4251  : _("Move Track Down");
4252 
4253  }
4254 
4255  longDesc = longDesc.Format(target->GetName());
4256 
4257  PushState(longDesc, shortDesc);
4258  GetTrackPanel()->Refresh(false);
4259 }
4260 
4261 void AudacityProject::OnInputDevice(const CommandContext &WXUNUSED(context) )
4262 {
4264  if (tb) {
4265  tb->ShowInputDialog();
4266  }
4267 }
4268 
4269 void AudacityProject::OnOutputDevice(const CommandContext &WXUNUSED(context) )
4270 {
4272  if (tb) {
4273  tb->ShowOutputDialog();
4274  }
4275 }
4276 
4277 void AudacityProject::OnAudioHost(const CommandContext &WXUNUSED(context) )
4278 {
4280  if (tb) {
4281  tb->ShowHostDialog();
4282  }
4283 }
4284 
4285 void AudacityProject::OnInputChannels(const CommandContext &WXUNUSED(context) )
4286 {
4288  if (tb) {
4289  tb->ShowChannelsDialog();
4290  }
4291 }
4292 
4293 void AudacityProject::OnOutputGain(const CommandContext &WXUNUSED(context) )
4294 {
4295  MixerToolBar *tb = GetMixerToolBar();
4296  if (tb) {
4297  tb->ShowOutputGainDialog();
4298  }
4299 }
4300 
4301 void AudacityProject::OnInputGain(const CommandContext &WXUNUSED(context) )
4302 {
4303  MixerToolBar *tb = GetMixerToolBar();
4304  if (tb) {
4305  tb->ShowInputGainDialog();
4306  }
4307 }
4308 
4309 void AudacityProject::OnOutputGainInc(const CommandContext &WXUNUSED(context) )
4310 {
4311  MixerToolBar *tb = GetMixerToolBar();
4312  if (tb) {
4313  tb->AdjustOutputGain(1);
4314  }
4315 }
4316 
4317 void AudacityProject::OnOutputGainDec(const CommandContext &WXUNUSED(context) )
4318 {
4319  MixerToolBar *tb = GetMixerToolBar();
4320  if (tb) {
4321  tb->AdjustOutputGain(-1);
4322  }
4323 }
4324 
4325 void AudacityProject::OnInputGainInc(const CommandContext &WXUNUSED(context) )
4326 {
4327  MixerToolBar *tb = GetMixerToolBar();
4328  if (tb) {
4329  tb->AdjustInputGain(1);
4330  }
4331 }
4332 
4333 void AudacityProject::OnInputGainDec(const CommandContext &WXUNUSED(context) )
4334 {
4335  MixerToolBar *tb = GetMixerToolBar();
4336  if (tb) {
4337  tb->AdjustInputGain(-1);
4338  }
4339 }
4340 
4341 void AudacityProject::OnPlayAtSpeed(const CommandContext &WXUNUSED(context) )
4342 {
4344  if (tb) {
4345  tb->PlayAtSpeed(false, false);
4346  }
4347 }
4348 
4350 {
4352  if (tb) {
4353  tb->PlayAtSpeed(true, false);
4354  }
4355 }
4356 
4358 {
4360  if (tb) {
4361  tb->PlayAtSpeed(false, true);
4362  }
4363 }
4364 
4365 void AudacityProject::OnSetPlaySpeed(const CommandContext &WXUNUSED(context) )
4366 {
4368  if (tb) {
4369  tb->ShowPlaySpeedDialog();
4370  }
4371 }
4372 
4373 void AudacityProject::OnPlaySpeedInc(const CommandContext &WXUNUSED(context) )
4374 {
4376  if (tb) {
4377  tb->AdjustPlaySpeed(0.1f);
4378  }
4379 }
4380 
4381 void AudacityProject::OnPlaySpeedDec(const CommandContext &WXUNUSED(context) )
4382 {
4384  if (tb) {
4385  tb->AdjustPlaySpeed(-0.1f);
4386  }
4387 }
4388 
4390 {
4391  // Window is 1/100th of a second.
4392  auto windowSize = size_t(std::max(1.0, GetRate() / 100));
4393  Floats dist{ windowSize, true };
4394 
4395  TrackListIterator iter(GetTracks());
4396  Track *track = iter.First();
4397  int nTracks = 0;
4398  while (track) {
4399  if (!track->GetSelected() || track->GetKind() != (Track::Wave)) {
4400  track = iter.Next();
4401  continue;
4402  }
4403  WaveTrack *one = (WaveTrack *)track;
4404  auto oneWindowSize = size_t(std::max(1.0, one->GetRate() / 100));
4405  Floats oneDist{ oneWindowSize };
4406  auto s = one->TimeToLongSamples(t0);
4407  // fillTwo to ensure that missing values are treated as 2, and hence do not
4408  // get used as zero crossings.
4409  one->Get((samplePtr)oneDist.get(), floatSample,
4410  s - (int)oneWindowSize/2, oneWindowSize, fillTwo);
4411 
4412 
4413  // Looking for actual crossings.
4414  double prev = 2.0;
4415  for(size_t i=0; i<oneWindowSize; i++){
4416  float fDist = fabs( oneDist[i]); // score is absolute value
4417  if( prev * oneDist[i] > 0 ) // both same sign? No good.
4418  fDist = fDist + 0.4; // No good if same sign.
4419  else if( prev > 0.0 )
4420  fDist = fDist + 0.1; // medium penalty for downward crossing.
4421  prev = oneDist[i];
4422  oneDist[i] = fDist;
4423  }
4424 
4425  // TODO: The mixed rate zero crossing code is broken,
4426  // if oneWindowSize > windowSize we'll miss out some
4427  // samples - so they will still be zero, so we'll use them.
4428  for(size_t i = 0; i < windowSize; i++) {
4429  size_t j;
4430  if (windowSize != oneWindowSize)
4431  j = i * (oneWindowSize-1) / (windowSize-1);
4432  else
4433  j = i;
4434 
4435  dist[i] += oneDist[j];
4436  // Apply a small penalty for distance from the original endpoint
4437  // We'll always prefer an upward
4438  dist[i] += 0.1 * (abs(int(i) - int(windowSize/2))) / float(windowSize/2);
4439  }
4440  nTracks++;
4441  track = iter.Next();
4442  }
4443 
4444  // Find minimum
4445  int argmin = 0;
4446  float min = 3.0;
4447  for(size_t i=0; i<windowSize; i++) {
4448  if (dist[i] < min) {
4449  argmin = i;
4450  min = dist[i];
4451  }
4452  }
4453 
4454  // If we're worse than 0.2 on average, on one track, then no good.
4455  if(( nTracks == 1 ) && ( min > (0.2*nTracks) ))
4456  return t0;
4457  // If we're worse than 0.6 on average, on multi-track, then no good.
4458  if(( nTracks > 1 ) && ( min > (0.6*nTracks) ))
4459  return t0;
4460 
4461  return t0 + (argmin - (int)windowSize/2)/GetRate();
4462 }
4463 
4464 void AudacityProject::OnZeroCrossing(const CommandContext &WXUNUSED(context) )
4465 {
4466  const double t0 = NearestZeroCrossing(mViewInfo.selectedRegion.t0());
4469  else {
4470  const double t1 = NearestZeroCrossing(mViewInfo.selectedRegion.t1());
4471  // Empty selection is generally not much use, so do not make it if empty.
4472  if( fabs( t1 - t0 ) * GetRate() > 1.5 )
4474  }
4475 
4476  ModifyState(false);
4477 
4478  mTrackPanel->Refresh(false);
4479 }
4480 
4481 
4485 bool AudacityProject::DoAudacityCommand(const PluginID & ID, const CommandContext & context, int flags)
4486 {
4487  const PluginDescriptor *plug = PluginManager::Get().GetPlugin(ID);
4488  if (!plug)
4489  return false;
4490 
4491  if (flags & OnEffectFlags::kConfigured)
4492  {
4493  OnStop(*this);
4494 // SelectAllIfNone();
4495  }
4496 
4498  bool success = em.DoAudacityCommand(ID,
4499  context,
4500  this,
4501  (flags & OnEffectFlags::kConfigured) == 0);
4502 
4503  if (!success)
4504  return false;
4505 
4506 /*
4507  if (em.GetSkipStateFlag())
4508  flags = flags | OnEffectFlags::kSkipState;
4509 
4510  if (!(flags & OnEffectFlags::kSkipState))
4511  {
4512  wxString shortDesc = em.GetCommandName(ID);
4513  wxString longDesc = em.GetCommandDescription(ID);
4514  PushState(longDesc, shortDesc);
4515  }
4516 */
4517  RedrawProject();
4518  return true;
4519 }
4520 
4521 
4522 
4523 //
4524 // Effect Menus
4525 //
4526 
4531 bool AudacityProject::DoEffect(const PluginID & ID, const CommandContext &WXUNUSED(context), int flags)
4532 {
4533  const PluginDescriptor *plug = PluginManager::Get().GetPlugin(ID);
4534  if (!plug)
4535  return false;
4536 
4537  EffectType type = plug->GetEffectType();
4538 
4539  // Make sure there's no activity since the effect is about to be applied
4540  // to the project's tracks. Mainly for Apply during RTP, but also used
4541  // for batch commands
4542  if (flags & OnEffectFlags::kConfigured)
4543  {
4544  OnStop(*this);
4545  SelectAllIfNone();
4546  }
4547 
4549 
4550  auto nTracksOriginally = GetTrackCount();
4551  TrackListIterator iter(GetTracks());
4552  Track *t = iter.First();
4553  WaveTrack *newTrack{};
4554  wxWindow *focus = wxWindow::FindFocus();
4555 
4556  bool success = false;
4557  auto cleanup = finally( [&] {
4558 
4559  if (!success) {
4560  if (newTrack) {
4561  mTracks->Remove(newTrack);
4562  mTrackPanel->Refresh(false);
4563  }
4564 
4565  // For now, we're limiting realtime preview to a single effect, so
4566  // make sure the menus reflect that fact that one may have just been
4567  // opened.
4568  UpdateMenus(false);
4569  }
4570 
4571  } );
4572 
4573  //double prevEndTime = mTracks->GetEndTime();
4574  int count = 0;
4575  bool clean = true;
4576  while (t) {
4577  if (t->GetSelected() && t->GetKind() == (Track::Wave)) {
4578  if (t->GetEndTime() != 0.0) clean = false;
4579  count++;
4580  }
4581  t = iter.Next();
4582  }
4583 
4584  if (count == 0) {
4585  // No tracks were selected...
4586  if (type == EffectTypeGenerate) {
4587  // Create a NEW track for the generated audio...
4588  newTrack = static_cast<WaveTrack*>(mTracks->Add(mTrackFactory->NewWaveTrack()));
4589  newTrack->SetSelected(true);
4590  }
4591  }
4592 
4594 
4595  success = em.DoEffect(ID, this, mRate,
4598  (flags & OnEffectFlags::kConfigured) == 0);
4599 
4600  if (!success)
4601  return false;
4602 
4603  if (em.GetSkipStateFlag())
4604  flags = flags | OnEffectFlags::kSkipState;
4605 
4606  if (!(flags & OnEffectFlags::kSkipState))
4607  {
4608  wxString shortDesc = em.GetCommandName(ID);
4609  wxString longDesc = em.GetCommandDescription(ID);
4610  PushState(longDesc, shortDesc);
4611  }
4612 
4613  if (!(flags & OnEffectFlags::kDontRepeatLast))
4614  {
4615  // Only remember a successful effect, don't remember insert,
4616  // or analyze effects.
4617  if (type == EffectTypeProcess) {
4618  wxString shortDesc = em.GetCommandName(ID);
4619  mLastEffect = ID;
4620  wxString lastEffectDesc;
4621  /* i18n-hint: %s will be the name of the effect which will be
4622  * repeated if this menu item is chosen */
4623  lastEffectDesc.Printf(_("Repeat %s"), shortDesc);
4624  mCommandManager.Modify(wxT("RepeatLastEffect"), lastEffectDesc);
4625  }
4626  }
4627 
4628  //STM:
4629  //The following automatically re-zooms after sound was generated.
4630  // IMO, it was disorienting, removing to try out without re-fitting
4631  //mchinen:12/14/08 reapplying for generate effects
4632  if (type == EffectTypeGenerate)
4633  {
4634  if (count == 0 || (clean && mViewInfo.selectedRegion.t0() == 0.0))
4635  OnZoomFit(*this);
4636  // mTrackPanel->Refresh(false);
4637  }
4638  RedrawProject();
4639  if (focus != nullptr) {
4640  focus->SetFocus();
4641  }
4642 
4643  // A fix for Bug 63
4644  // New tracks added? Scroll them into view so that user sees them.
4645  // Don't care what track type. An analyser might just have added a
4646  // Label track and we want to see it.
4647  if( GetTrackCount() > nTracksOriginally ){
4648  // 0.0 is min scroll position, 1.0 is max scroll position.
4649  GetTrackPanel()->VerticalScroll( 1.0 );
4650  } else {
4652  mTrackPanel->Refresh(false);
4653  }
4654 
4655  return true;
4656 }
4657 
4659 {
4660  DoEffect(context.parameter, context, 0);
4661 }
4662 
4664 {
4665  if (!mLastEffect.IsEmpty())
4666  {
4668  }
4669 }
4670 
4671 
4673  for( size_t i = 0; i < gAudacityProjects.size(); i++ ) {
4674  AudacityProject *p = gAudacityProjects[i].get();
4675 
4676  p->RebuildMenuBar();
4677 #if defined(__WXGTK__)
4678  // Workaround for:
4679  //
4680  // http://bugzilla.audacityteam.org/show_bug.cgi?id=458
4681  //
4682  // This workaround should be removed when Audacity updates to wxWidgets 3.x which has a fix.
4683  wxRect r = p->GetRect();
4684  p->SetSize(wxSize(1,1));
4685  p->SetSize(r.GetSize());
4686 #endif
4687  }
4688 }
4689 
4691 {
4692  if (PluginManager::Get().ShowManager(this, type))
4694 }
4695 
4697 {
4699 }
4700 
4701 void AudacityProject::OnManageEffects(const CommandContext &WXUNUSED(context) )
4702 {
4704 }
4705 
4707 {
4709 }
4710 
4711 void AudacityProject::OnManageTools(const CommandContext &WXUNUSED(context) )
4712 {
4714 }
4715 
4716 
4718 {
4719  DoEffect(EffectManager::Get().GetEffectByIdentifier(wxT("StereoToMono")),
4720  context,
4722 }
4723 
4725 {
4726  wxLogDebug( "Command was: %s", ctx.parameter);
4727  DoAudacityCommand(EffectManager::Get().GetEffectByIdentifier(ctx.parameter),
4728  ctx,
4729  OnEffectFlags::kNone); // Not configured, so prompt user.
4730 }
4731 
4732 //
4733 // File Menu
4734 //
4735 
4736 void AudacityProject::OnNew(const CommandContext &WXUNUSED(context) )
4737 {
4739 }
4740 
4741 void AudacityProject::OnOpen(const CommandContext &WXUNUSED(context) )
4742 {
4743  OpenFiles(this);
4744 }
4745 
4746 void AudacityProject::OnClose(const CommandContext &WXUNUSED(context) )
4747 {
4748  mMenuClose = true;
4749  Close();
4750 }
4751 
4752 void AudacityProject::OnSave(const CommandContext &WXUNUSED(context) )
4753 {
4754  Save();
4755 }
4756 
4757 void AudacityProject::OnSaveAs(const CommandContext &WXUNUSED(context) )
4758 {
4759  SaveAs();
4760 }
4761