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