1 
2 
3 #include "../AudioIO.h"
4 #include "../BatchProcessDialog.h"
5 #include "../Benchmark.h"
6 #include "../CommonCommandFlags.h"
7 #include "../Journal.h"
8 #include "../Menus.h"
9 #include "../PluginManager.h"
10 #include "../PluginRegistrationDialog.h"
11 #include "Prefs.h"
12 #include "Project.h"
13 #include "../ProjectSettings.h"
14 #include "../ProjectWindow.h"
15 #include "../ProjectWindows.h"
16 #include "../ProjectSelectionManager.h"
17 #include "../toolbars/ToolManager.h"
18 #include "../Screenshot.h"
19 #include "TempDirectory.h"
20 #include "../UndoManager.h"
21 #include "../commands/CommandContext.h"
22 #include "../commands/CommandManager.h"
23 #include "../commands/ScreenshotCommand.h"
24 #include "../effects/EffectManager.h"
25 #include "../effects/EffectUI.h"
26 #include "../effects/RealtimeEffectManager.h"
27 #include "../prefs/EffectsPrefs.h"
28 #include "../prefs/PrefsDialog.h"
29 #include "../widgets/AudacityMessageBox.h"
30 
31 #include <wx/log.h>
32 
33 // private helper classes and functions
34 namespace {
35 
36 AttachedWindows::RegisteredFactory sMacrosWindowKey{
__anon4df4c4080202( ) 37    []( AudacityProject &parent ) -> wxWeakRef< wxWindow > {
38       auto &window = ProjectWindow::Get( parent );
39       return safenew MacrosWindow(
40          &window, parent, true
41       );
42    }
43 };
44 
ShowManager(PluginManager & pm,wxWindow * parent,EffectType type)45 bool ShowManager(
46    PluginManager &pm, wxWindow *parent, EffectType type)
47 {
48    pm.CheckForUpdates();
49 
50    PluginRegistrationDialog dlg(parent, type);
51    return dlg.ShowModal() == wxID_OK;
52 }
53 
DoManagePluginsMenu(AudacityProject & project,EffectType type)54 void DoManagePluginsMenu(AudacityProject &project, EffectType type)
55 {
56    auto &window = GetProjectFrame( project );
57    auto &pm = PluginManager::Get();
58    if (ShowManager(pm, &window, type))
59       MenuCreator::RebuildAllMenuBars();
60 }
61 
CompareEffectsByName(const PluginDescriptor * a,const PluginDescriptor * b)62 bool CompareEffectsByName(const PluginDescriptor *a, const PluginDescriptor *b)
63 {
64    return
65       std::make_pair( a->GetSymbol().Translation(), a->GetPath() ) <
66       std::make_pair( b->GetSymbol().Translation(), b->GetPath() );
67 }
68 
CompareEffectsByPublisher(const PluginDescriptor * a,const PluginDescriptor * b)69 bool CompareEffectsByPublisher(
70    const PluginDescriptor *a, const PluginDescriptor *b)
71 {
72    auto &em = EffectManager::Get();
73 
74    auto akey = em.GetVendorName(a->GetID());
75    auto bkey = em.GetVendorName(b->GetID());
76 
77    if (akey.empty())
78       akey = XO("Uncategorized");
79    if (bkey.empty())
80       bkey = XO("Uncategorized");
81 
82    return
83       std::make_tuple(
84          akey.Translation(), a->GetSymbol().Translation(), a->GetPath() ) <
85       std::make_tuple(
86          bkey.Translation(), b->GetSymbol().Translation(), b->GetPath() );
87 }
88 
CompareEffectsByPublisherAndName(const PluginDescriptor * a,const PluginDescriptor * b)89 bool CompareEffectsByPublisherAndName(
90    const PluginDescriptor *a, const PluginDescriptor *b)
91 {
92    auto &em = EffectManager::Get();
93    auto akey = em.GetVendorName(a->GetID());
94    auto bkey = em.GetVendorName(b->GetID());
95 
96    if (a->IsEffectDefault())
97       akey = {};
98    if (b->IsEffectDefault())
99       bkey = {};
100 
101    return
102       std::make_tuple(
103          akey.Translation(), a->GetSymbol().Translation(), a->GetPath() ) <
104       std::make_tuple(
105          bkey.Translation(), b->GetSymbol().Translation(), b->GetPath() );
106 }
107 
CompareEffectsByTypeAndName(const PluginDescriptor * a,const PluginDescriptor * b)108 bool CompareEffectsByTypeAndName(
109    const PluginDescriptor *a, const PluginDescriptor *b)
110 {
111    auto &em = EffectManager::Get();
112    auto akey = em.GetEffectFamilyName(a->GetID());
113    auto bkey = em.GetEffectFamilyName(b->GetID());
114 
115    if (akey.empty())
116       akey = XO("Uncategorized");
117    if (bkey.empty())
118       bkey = XO("Uncategorized");
119 
120    if (a->IsEffectDefault())
121       akey = {};
122    if (b->IsEffectDefault())
123       bkey = {};
124 
125    return
126       std::make_tuple(
127          akey.Translation(), a->GetSymbol().Translation(), a->GetPath() ) <
128       std::make_tuple(
129          bkey.Translation(), b->GetSymbol().Translation(), b->GetPath() );
130 }
131 
CompareEffectsByType(const PluginDescriptor * a,const PluginDescriptor * b)132 bool CompareEffectsByType(const PluginDescriptor *a, const PluginDescriptor *b)
133 {
134    auto &em = EffectManager::Get();
135    auto akey = em.GetEffectFamilyName(a->GetID());
136    auto bkey = em.GetEffectFamilyName(b->GetID());
137 
138    if (akey.empty())
139       akey = XO("Uncategorized");
140    if (bkey.empty())
141       bkey = XO("Uncategorized");
142 
143    return
144       std::make_tuple(
145          akey.Translation(), a->GetSymbol().Translation(), a->GetPath() ) <
146       std::make_tuple(
147          bkey.Translation(), b->GetSymbol().Translation(), b->GetPath() );
148 }
149 
150 // Forward-declared function has its definition below with OnEffect in view
151 void AddEffectMenuItemGroup(
152    MenuTable::BaseItemPtrs &table,
153    const TranslatableStrings & names,
154    const PluginIDs & plugs,
155    const std::vector<CommandFlag> & flags,
156    bool isDefault);
157 
AddEffectMenuItems(MenuTable::BaseItemPtrs & table,std::vector<const PluginDescriptor * > & plugs,CommandFlag batchflags,CommandFlag realflags,bool isDefault)158 void AddEffectMenuItems(
159    MenuTable::BaseItemPtrs &table,
160    std::vector<const PluginDescriptor*> & plugs,
161    CommandFlag batchflags,
162    CommandFlag realflags,
163    bool isDefault)
164 {
165    size_t pluginCnt = plugs.size();
166 
167    auto groupBy = EffectsGroupBy.Read();
168 
169    bool grouped = false;
170    if (groupBy.StartsWith(wxT("groupby")))
171    {
172       grouped = true;
173    }
174 
175    // Some weird special case stuff just for Noise Reduction so that there is
176    // more informative help
177    const auto getBatchFlags = [&]( const PluginDescriptor *plug ){
178       if ( plug->GetSymbol().Msgid() == XO( "Noise Reduction" ) )
179          return
180             ( batchflags | NoiseReductionTimeSelectedFlag() ) & ~TimeSelectedFlag();
181       return batchflags;
182    };
183 
184    TranslatableStrings groupNames;
185    PluginIDs groupPlugs;
186    std::vector<CommandFlag> groupFlags;
187    if (grouped)
188    {
189       TranslatableString last;
190       TranslatableString current;
191 
192       for (size_t i = 0; i < pluginCnt; i++)
193       {
194          const PluginDescriptor *plug = plugs[i];
195 
196          auto name = plug->GetSymbol().Msgid();
197 
198          if (plug->IsEffectInteractive())
199             name += XO("...");
200 
201          if (groupBy == wxT("groupby:publisher"))
202          {
203             current = EffectManager::Get().GetVendorName(plug->GetID());
204             if (current.empty())
205             {
206                current = XO("Unknown");
207             }
208          }
209          else if (groupBy == wxT("groupby:type"))
210          {
211             current = EffectManager::Get().GetEffectFamilyName(plug->GetID());
212             if (current.empty())
213             {
214                current = XO("Unknown");
215             }
216          }
217 
218          if (current != last)
219          {
220             using namespace MenuTable;
221             BaseItemPtrs temp;
222             bool bInSubmenu = !last.empty() && (groupNames.size() > 1);
223 
224             AddEffectMenuItemGroup(temp,
225                groupNames,
226                groupPlugs, groupFlags, isDefault);
227 
228             table.push_back( MenuOrItems( wxEmptyString,
229                ( bInSubmenu ? last : TranslatableString{} ), std::move( temp )
230             ) );
231 
232             groupNames.clear();
233             groupPlugs.clear();
234             groupFlags.clear();
235             last = current;
236          }
237 
238          groupNames.push_back( name );
239          groupPlugs.push_back(plug->GetID());
240          groupFlags.push_back(
241             plug->IsEffectRealtime() ? realflags : getBatchFlags( plug ) );
242       }
243 
244       if (groupNames.size() > 0)
245       {
246          using namespace MenuTable;
247          BaseItemPtrs temp;
248          bool bInSubmenu = groupNames.size() > 1;
249 
250          AddEffectMenuItemGroup(temp,
251             groupNames, groupPlugs, groupFlags, isDefault);
252 
253          table.push_back( MenuOrItems( wxEmptyString,
254             ( bInSubmenu ? current : TranslatableString{} ), std::move( temp )
255          ) );
256       }
257    }
258    else
259    {
260       for (size_t i = 0; i < pluginCnt; i++)
261       {
262          const PluginDescriptor *plug = plugs[i];
263 
264          auto name = plug->GetSymbol().Msgid();
265 
266          if (plug->IsEffectInteractive())
267             name += XO("...");
268 
269          TranslatableString group;
270          if (groupBy == wxT("sortby:publisher:name"))
271          {
272             group = EffectManager::Get().GetVendorName(plug->GetID());
273          }
274          else if (groupBy == wxT("sortby:type:name"))
275          {
276             group = EffectManager::Get().GetEffectFamilyName(plug->GetID());
277          }
278 
279          if (plug->IsEffectDefault())
280          {
281             group = {};
282          }
283 
284          groupNames.push_back(
285             group.empty()
286                ? name
287                : XO("%s: %s").Format( group, name )
288          );
289 
290          groupPlugs.push_back(plug->GetID());
291          groupFlags.push_back(
292             plug->IsEffectRealtime() ? realflags : getBatchFlags( plug ) );
293       }
294 
295       if (groupNames.size() > 0)
296       {
297          AddEffectMenuItemGroup(
298             table, groupNames, groupPlugs, groupFlags, isDefault);
299       }
300 
301    }
302 
303    return;
304 }
305 
306 /// The effects come from a plug in list
307 /// This code iterates through the list, adding effects into
308 /// the menu.
PopulateEffectsMenu(EffectType type,CommandFlag batchflags,CommandFlag realflags)309 MenuTable::BaseItemPtrs PopulateEffectsMenu(
310    EffectType type,
311    CommandFlag batchflags,
312    CommandFlag realflags)
313 {
314    MenuTable::BaseItemPtrs result;
315    PluginManager & pm = PluginManager::Get();
316 
317    std::vector<const PluginDescriptor*> defplugs;
318    std::vector<const PluginDescriptor*> optplugs;
319 
320    EffectManager & em = EffectManager::Get();
321    for (auto &plugin : pm.EffectsOfType(type)) {
322       auto plug = &plugin;
323       if( plug->IsInstantiated() && em.IsHidden(plug->GetID()) )
324          continue;
325       if ( !plug->IsEnabled() ){
326          ;// don't add to menus!
327       }
328       else if (plug->IsEffectDefault()
329 #ifdef EXPERIMENTAL_DA
330          // Move Nyquist prompt into nyquist group.
331          && (plug->GetSymbol() !=
332                ComponentInterfaceSymbol("Nyquist Effects Prompt"))
333          && (plug->GetSymbol() != ComponentInterfaceSymbol("Nyquist Tools Prompt"))
334          && (plug->GetSymbol() != ComponentInterfaceSymbol(NYQUIST_PROMPT_ID))
335 #endif
336          )
337          defplugs.push_back(plug);
338       else
339          optplugs.push_back(plug);
340    }
341 
342    wxString groupby = EffectsGroupBy.Read();
343 
344    using Comparator = bool(*)(const PluginDescriptor*, const PluginDescriptor*);
345    Comparator comp1, comp2;
346    if (groupby == wxT("sortby:name"))
347       comp1 = comp2 = CompareEffectsByName;
348    else if (groupby == wxT("sortby:publisher:name"))
349       comp1 = CompareEffectsByName, comp2 = CompareEffectsByPublisherAndName;
350    else if (groupby == wxT("sortby:type:name"))
351       comp1 = CompareEffectsByName, comp2 = CompareEffectsByTypeAndName;
352    else if (groupby == wxT("groupby:publisher"))
353       comp1 = comp2 = CompareEffectsByPublisher;
354    else if (groupby == wxT("groupby:type"))
355       comp1 = comp2 = CompareEffectsByType;
356    else // name
357       comp1 = comp2 = CompareEffectsByName;
358 
359    std::sort( defplugs.begin(), defplugs.end(), comp1 );
360    std::sort( optplugs.begin(), optplugs.end(), comp2 );
361 
362    MenuTable::BaseItemPtrs section1;
363    AddEffectMenuItems( section1, defplugs, batchflags, realflags, true );
364 
365    MenuTable::BaseItemPtrs section2;
366    AddEffectMenuItems( section2, optplugs, batchflags, realflags, false );
367 
368    bool section = !section1.empty() && !section2.empty();
369    result.push_back( MenuTable::Items( "", std::move( section1 ) ) );
370    if ( section )
371       result.push_back( MenuTable::Section( "", std::move( section2 ) ) );
372    else
373       result.push_back( MenuTable::Items( "", std::move( section2 ) ) );
374 
375    return result;
376 }
377 
378 // Forward-declared function has its definition below with OnApplyMacroDirectly
379 // in view
380 MenuTable::BaseItemPtrs PopulateMacrosMenu( CommandFlag flags  );
381 
382 }
383 
384 namespace PluginActions {
385 
386 // Menu handler functions
387 
388 struct Handler : CommandHandlerObject {
389 
OnResetConfigPluginActions::Handler390 void OnResetConfig(const CommandContext &context)
391 {
392    auto &project = context.project;
393    auto &menuManager = MenuManager::Get(project);
394    menuManager.mLastAnalyzerRegistration = MenuCreator::repeattypenone;
395    menuManager.mLastToolRegistration = MenuCreator::repeattypenone;
396    menuManager.mLastGenerator = "";
397    menuManager.mLastEffect = "";
398    menuManager.mLastAnalyzer = "";
399    menuManager.mLastTool = "";
400 
401    ResetPreferences();
402 
403    // Directory will be reset on next restart.
404    FileNames::UpdateDefaultPath(FileNames::Operation::Temp, TempDirectory::DefaultTempDir());
405 
406    // There are many more things we could reset here.
407    // Beeds discussion as to which make sense to.
408    // Maybe in future versions?
409    // - Reset Effects
410    // - Reset Recording and Playback volumes
411    // - Reset Selection formats (and for spectral too)
412    // - Reset Play-at-speed speed to x1
413    // - Stop playback/recording and unapply pause.
414    // - Set Zoom sensibly.
415    gPrefs->Write("/GUI/SyncLockTracks", 0);
416    gPrefs->Write("/AudioIO/SoundActivatedRecord", 0);
417    gPrefs->Write("/SelectionToolbarMode", 0);
418    gPrefs->Flush();
419    DoReloadPreferences(project);
420    ToolManager::OnResetToolBars(context);
421 
422    // These are necessary to preserve the newly correctly laid out toolbars.
423    // In particular the Device Toolbar ends up short on next restart,
424    // if they are left out.
425    gPrefs->Write(wxT("/PrefsVersion"), wxString(wxT(AUDACITY_PREFS_VERSION_STRING)));
426 
427    // write out the version numbers to the prefs file for future checking
428    gPrefs->Write(wxT("/Version/Major"), AUDACITY_VERSION);
429    gPrefs->Write(wxT("/Version/Minor"), AUDACITY_RELEASE);
430    gPrefs->Write(wxT("/Version/Micro"), AUDACITY_REVISION);
431 
432    gPrefs->Flush();
433 
434    ProjectSelectionManager::Get( project )
435       .AS_SetSnapTo(gPrefs->ReadLong("/SnapTo", SNAP_OFF));
436    ProjectSelectionManager::Get( project )
437       .AS_SetRate(gPrefs->ReadDouble("/DefaultProjectSampleRate", 44100.0));
438 }
439 
OnManageGeneratorsPluginActions::Handler440 void OnManageGenerators(const CommandContext &context)
441 {
442    auto &project = context.project;
443    DoManagePluginsMenu(project, EffectTypeGenerate);
444 }
445 
OnEffectPluginActions::Handler446 void OnEffect(const CommandContext &context)
447 {
448    // using GET to interpret parameter as a PluginID
449    EffectUI::DoEffect(context.parameter.GET(), context, 0);
450 }
451 
OnManageEffectsPluginActions::Handler452 void OnManageEffects(const CommandContext &context)
453 {
454    auto &project = context.project;
455    DoManagePluginsMenu(project, EffectTypeProcess);
456 }
457 
OnAnalyzer2PluginActions::Handler458 void OnAnalyzer2(wxCommandEvent& evt) { return; }
459 
OnRepeatLastGeneratorPluginActions::Handler460 void OnRepeatLastGenerator(const CommandContext &context)
461 {
462    auto& menuManager = MenuManager::Get(context.project);
463    auto lastEffect = menuManager.mLastGenerator;
464    if (!lastEffect.empty())
465    {
466       EffectUI::DoEffect(
467          lastEffect, context, menuManager.mRepeatGeneratorFlags | EffectManager::kRepeatGen);
468    }
469 }
470 
OnRepeatLastEffectPluginActions::Handler471 void OnRepeatLastEffect(const CommandContext &context)
472 {
473    auto& menuManager = MenuManager::Get(context.project);
474    auto lastEffect = menuManager.mLastEffect;
475    if (!lastEffect.empty())
476    {
477       EffectUI::DoEffect(
478          lastEffect, context, menuManager.mRepeatEffectFlags);
479    }
480 }
481 
OnRepeatLastAnalyzerPluginActions::Handler482 void OnRepeatLastAnalyzer(const CommandContext& context)
483 {
484    auto& menuManager = MenuManager::Get(context.project);
485    switch (menuManager.mLastAnalyzerRegistration) {
486    case MenuCreator::repeattypeplugin:
487      {
488        auto lastEffect = menuManager.mLastAnalyzer;
489        if (!lastEffect.empty())
490        {
491          EffectUI::DoEffect(
492             lastEffect, context, menuManager.mRepeatAnalyzerFlags);
493        }
494      }
495       break;
496    case MenuCreator::repeattypeunique:
497       CommandManager::Get(context.project).DoRepeatProcess(context,
498          menuManager.mLastAnalyzerRegisteredId);
499       break;
500    }
501 }
502 
OnRepeatLastToolPluginActions::Handler503 void OnRepeatLastTool(const CommandContext& context)
504 {
505    auto& menuManager = MenuManager::Get(context.project);
506    switch (menuManager.mLastToolRegistration) {
507      case MenuCreator::repeattypeplugin:
508      {
509         auto lastEffect = menuManager.mLastTool;
510         if (!lastEffect.empty())
511         {
512            EffectUI::DoEffect(
513               lastEffect, context, menuManager.mRepeatToolFlags);
514         }
515      }
516        break;
517      case MenuCreator::repeattypeunique:
518         CommandManager::Get(context.project).DoRepeatProcess(context,
519            menuManager.mLastToolRegisteredId);
520         break;
521      case MenuCreator::repeattypeapplymacro:
522         OnApplyMacroDirectlyByName(context, menuManager.mLastTool);
523         break;
524    }
525 }
526 
527 
OnManageAnalyzersPluginActions::Handler528 void OnManageAnalyzers(const CommandContext &context)
529 {
530    auto &project = context.project;
531    DoManagePluginsMenu(project, EffectTypeAnalyze);
532 }
533 
OnManageToolsPluginActions::Handler534 void OnManageTools(const CommandContext &context )
535 {
536    auto &project = context.project;
537    DoManagePluginsMenu(project, EffectTypeTool);
538 }
539 
OnManageMacrosPluginActions::Handler540 void OnManageMacros(const CommandContext &context )
541 {
542    auto &project = context.project;
543    CommandManager::Get(project).RegisterLastTool(context);  //Register Macros as Last Tool
544    auto macrosWindow = &GetAttachedWindows(project)
545       .AttachedWindows::Get< MacrosWindow >( sMacrosWindowKey );
546    if (macrosWindow) {
547       macrosWindow->Show();
548       macrosWindow->Raise();
549       macrosWindow->UpdateDisplay( true );
550    }
551 }
552 
OnApplyMacrosPalettePluginActions::Handler553 void OnApplyMacrosPalette(const CommandContext &context )
554 {
555    auto &project = context.project;
556    CommandManager::Get(project).RegisterLastTool(context);  //Register Palette as Last Tool
557    auto macrosWindow = &GetAttachedWindows(project)
558       .AttachedWindows::Get< MacrosWindow >( sMacrosWindowKey );
559    if (macrosWindow) {
560       macrosWindow->Show();
561       macrosWindow->Raise();
562       macrosWindow->UpdateDisplay( false );
563    }
564 }
565 
OnScreenshotPluginActions::Handler566 void OnScreenshot(const CommandContext &context )
567 {
568    CommandManager::Get(context.project).RegisterLastTool(context);  //Register Screenshot as Last Tool
569    ::OpenScreenshotTools( context.project );
570 }
571 
OnBenchmarkPluginActions::Handler572 void OnBenchmark(const CommandContext &context)
573 {
574    auto &project = context.project;
575    CommandManager::Get(project).RegisterLastTool(context);  //Register Run Benchmark as Last Tool
576    auto &window = GetProjectFrame( project );
577    ::RunBenchmark( &window, project);
578 }
579 
OnSimulateRecordingErrorsPluginActions::Handler580 void OnSimulateRecordingErrors(const CommandContext &context)
581 {
582    auto &project = context.project;
583    auto &commandManager = CommandManager::Get( project );
584 
585    auto gAudioIO = AudioIO::Get();
586    bool &setting = gAudioIO->mSimulateRecordingErrors;
587    commandManager.Check(wxT("SimulateRecordingErrors"), !setting);
588    setting = !setting;
589 }
590 
OnDetectUpstreamDropoutsPluginActions::Handler591 void OnDetectUpstreamDropouts(const CommandContext &context)
592 {
593    auto &project = context.project;
594    auto &commandManager = CommandManager::Get( project );
595 
596    auto gAudioIO = AudioIO::Get();
597    bool &setting = gAudioIO->mDetectUpstreamDropouts;
598    commandManager.Check(wxT("DetectUpstreamDropouts"), !setting);
599    setting = !setting;
600 }
601 
OnWriteJournalPluginActions::Handler602 void OnWriteJournal(const CommandContext &)
603 {
604    auto OnMessage =
605       /* i18n-hint a "journal" is a text file that records
606        the user's interactions with the application */
607       XO("A journal will be recorded after Audacity restarts.");
608    auto OffMessage =
609       /* i18n-hint a "journal" is a text file that records
610        the user's interactions with the application */
611       XO("No journal will be recorded after Audacity restarts.");
612 
613    using namespace Journal;
614    bool enabled = RecordEnabled();
615    if ( SetRecordEnabled(!enabled) )
616       enabled = !enabled;
617    if ( enabled )
618       AudacityMessageBox( OnMessage );
619    else
620       AudacityMessageBox( OffMessage );
621 }
622 
OnApplyMacroDirectlyPluginActions::Handler623 void OnApplyMacroDirectly(const CommandContext &context )
624 {
625    const MacroID& Name = context.parameter.GET();
626    OnApplyMacroDirectlyByName(context, Name);
627 }
OnApplyMacroDirectlyByNamePluginActions::Handler628 void OnApplyMacroDirectlyByName(const CommandContext& context, const MacroID& Name)
629 {
630    auto &project = context.project;
631    auto &window = ProjectWindow::Get( project );
632    //wxLogDebug( "Macro was: %s", context.parameter);
633    ApplyMacroDialog dlg( &window, project );
634    //const auto &Name = context.parameter;
635 
636 // We used numbers previously, but macros could get renumbered, making
637 // macros containing macros unpredictable.
638 #ifdef MACROS_BY_NUMBERS
639    long item=0;
640    // Take last three letters (of e.g. Macro007) and convert to a number.
641    Name.Mid( Name.length() - 3 ).ToLong( &item, 10 );
642    dlg.ApplyMacroToProject( item, false );
643 #else
644    dlg.ApplyMacroToProject( Name, false );
645 #endif
646    /* i18n-hint: %s will be the name of the macro which will be
647     * repeated if this menu item is chosen */
648    MenuManager::ModifyUndoMenuItems( project );
649 
650    TranslatableString desc;
651    EffectManager& em = EffectManager::Get();
652    auto shortDesc = em.GetCommandName(Name);
653    auto& undoManager = UndoManager::Get(project);
654    auto& commandManager = CommandManager::Get(project);
655    int cur = undoManager.GetCurrentState();
656    if (undoManager.UndoAvailable()) {
657        undoManager.GetShortDescription(cur, &desc);
658        commandManager.Modify(wxT("RepeatLastTool"), XXO("&Repeat %s")
659           .Format(desc));
660        auto& menuManager = MenuManager::Get(project);
661        menuManager.mLastTool = Name;
662        menuManager.mLastToolRegistration = MenuCreator::repeattypeapplymacro;
663    }
664 
665 }
666 
OnAudacityCommandPluginActions::Handler667 void OnAudacityCommand(const CommandContext & ctx)
668 {
669    // using GET in a log message for devs' eyes only
670    wxLogDebug( "Command was: %s", ctx.parameter.GET());
671    // Not configured, so prompt user.
672    MacroCommands::DoAudacityCommand(
673       EffectManager::Get().GetEffectByIdentifier(ctx.parameter),
674       ctx, EffectManager::kNone);
675 }
676 
677 }; // struct Handler
678 
679 } // namespace
680 
findCommandHandler(AudacityProject &)681 static CommandHandlerObject &findCommandHandler(AudacityProject &) {
682    // Handler is not stateful.  Doesn't need a factory registered with
683    // AudacityProject.
684    static PluginActions::Handler instance;
685    return instance;
686 };
687 
688 // Menu definitions? ...
689 
690 #define FN(X) (& PluginActions::Handler :: X)
691 
692 // ... buf first some more helper definitions, which use FN
693 namespace {
694 
AddEffectMenuItemGroup(MenuTable::BaseItemPtrs & table,const TranslatableStrings & names,const PluginIDs & plugs,const std::vector<CommandFlag> & flags,bool isDefault)695 void AddEffectMenuItemGroup(
696    MenuTable::BaseItemPtrs &table,
697    const TranslatableStrings & names,
698    const PluginIDs & plugs,
699    const std::vector<CommandFlag> & flags,
700    bool isDefault)
701 {
702    const int namesCnt = (int) names.size();
703    int perGroup;
704 
705 #if defined(__WXGTK__)
706    gPrefs->Read(wxT("/Effects/MaxPerGroup"), &perGroup, 15);
707 #else
708    gPrefs->Read(wxT("/Effects/MaxPerGroup"), &perGroup, 0);
709 #endif
710 
711    int groupCnt = namesCnt;
712    for (int i = 0; i < namesCnt; i++)
713    {
714       // compare full translations not msgids!
715       while (i + 1 < namesCnt && names[i].Translation() == names[i + 1].Translation())
716       {
717          i++;
718          groupCnt--;
719       }
720    }
721 
722    // The "default" effects shouldn't be broken into subgroups
723    if (namesCnt > 0 && isDefault)
724    {
725       perGroup = 0;
726    }
727 
728    int max = perGroup;
729    int items = perGroup;
730 
731    if (max > groupCnt)
732    {
733       max = 0;
734    }
735 
736    using namespace MenuTable;
737    // This finder scope may be redundant, but harmless
738    auto scope = FinderScope( findCommandHandler );
739    auto pTable = &table;
740    BaseItemPtrs temp1;
741 
742    int groupNdx = 0;
743    for (int i = 0; i < namesCnt; i++)
744    {
745       if (max > 0 && items == max)
746       {
747          // start collecting items for the next submenu
748          pTable = &temp1;
749       }
750 
751       // compare full translations not msgids!
752       if (i + 1 < namesCnt && names[i].Translation() == names[i + 1].Translation())
753       {
754          // collect a sub-menu for like-named items
755          const auto name = names[i];
756          const auto translation = name.Translation();
757          BaseItemPtrs temp2;
758          // compare full translations not msgids!
759          while (i < namesCnt && names[i].Translation() == translation)
760          {
761             const PluginDescriptor *plug =
762                PluginManager::Get().GetPlugin(plugs[i]);
763             wxString item = plug->GetPath();
764             if( plug->GetPluginType() == PluginTypeEffect )
765                temp2.push_back( Command( item,
766                   Verbatim( item ),
767                   FN(OnEffect),
768                   flags[i],
769                   CommandManager::Options{}
770                      .IsEffect()
771                      .AllowInMacros()
772                      .Parameter( plugs[i] ) ) );
773 
774             i++;
775          }
776          pTable->push_back( Menu( wxEmptyString, name, std::move( temp2 ) ) );
777          i--;
778       }
779       else
780       {
781          // collect one item
782          const PluginDescriptor *plug =
783             PluginManager::Get().GetPlugin(plugs[i]);
784          if( plug->GetPluginType() == PluginTypeEffect )
785             pTable->push_back( Command(
786                // Call Debug() not MSGID() so that any concatenated "..." is
787                // included in the identifier, preserving old behavior, and
788                // avoiding the collision of the "Silence" command and the
789                // "Silence..." generator
790                names[i].Debug(), // names[i].MSGID(),
791                names[i],
792                FN(OnEffect),
793                flags[i],
794                CommandManager::Options{}
795                   .IsEffect()
796                   .AllowInMacros()
797                   .Parameter( plugs[i] ) ) );
798       }
799 
800       if (max > 0)
801       {
802          items--;
803          if (items == 0 || i + 1 == namesCnt)
804          {
805             int end = groupNdx + max;
806             if (end + 1 > groupCnt)
807             {
808                end = groupCnt;
809             }
810             // Done collecting
811             table.push_back( Menu( wxEmptyString,
812                XXO("Plug-in %d to %d").Format( groupNdx + 1, end ),
813                std::move( temp1 )
814             ) );
815             items = max;
816             pTable = &table;
817             groupNdx += max;
818          }
819       }
820    }
821 
822    return;
823 }
824 
PopulateMacrosMenu(CommandFlag flags)825 MenuTable::BaseItemPtrs PopulateMacrosMenu( CommandFlag flags  )
826 {
827    MenuTable::BaseItemPtrs result;
828    auto names = MacroCommands::GetNames(); // these names come from filenames
829    int i;
830 
831    // This finder scope may be redundant, but harmless
832    auto scope = MenuTable::FinderScope( findCommandHandler );
833    for (i = 0; i < (int)names.size(); i++) {
834       auto MacroID = ApplyMacroDialog::MacroIdOfName( names[i] );
835       result.push_back( MenuTable::Command( MacroID,
836          Verbatim( names[i] ), // file name verbatim
837          FN(OnApplyMacroDirectly),
838          flags,
839          CommandManager::Options{}.AllowInMacros()
840       ) );
841    }
842 
843    return result;
844 }
845 
846 }
847 
848 // Menu definitions
849 
850 // Under /MenuBar
851 namespace {
852 using namespace MenuTable;
853 
854 const ReservedCommandFlag&
HasLastGeneratorFlag()855    HasLastGeneratorFlag() { static ReservedCommandFlag flag{
856       [](const AudacityProject &project){
857          return !MenuManager::Get( project ).mLastGenerator.empty();
858       }
859    }; return flag; }
860 
GenerateMenu()861 BaseItemSharedPtr GenerateMenu()
862 {
863    // All of this is a bit hacky until we can get more things connected into
864    // the plugin manager...sorry! :-(
865 
866    using Options = CommandManager::Options;
867 
868    static BaseItemSharedPtr menu{
869    ( FinderScope{ findCommandHandler },
870    Menu( wxT("Generate"), XXO("&Generate"),
871 #ifdef EXPERIMENTAL_EFFECT_MANAGEMENT
872       Section( "Manage",
873          Command( wxT("ManageGenerators"), XXO("Add / Remove Plug-ins..."),
874             FN(OnManageGenerators), AudioIONotBusyFlag() )
875       ),
876 #endif
877 
878       Section("RepeatLast",
879          // Delayed evaluation:
880          [](AudacityProject &project)
881          {
882             const auto &lastGenerator = MenuManager::Get(project).mLastGenerator;
883             TranslatableString buildMenuLabel;
884             if (!lastGenerator.empty())
885                buildMenuLabel = XO("Repeat %s")
886                   .Format(EffectManager::Get().GetCommandName(lastGenerator));
887             else
888                buildMenuLabel = XO("Repeat Last Generator");
889 
890             return Command(wxT("RepeatLastGenerator"), buildMenuLabel,
891                FN(OnRepeatLastGenerator),
892                AudioIONotBusyFlag() |
893                    HasLastGeneratorFlag(),
894                Options{}.IsGlobal(), findCommandHandler);
895          }
896       ),
897 
898       Section( "Generators",
899          // Delayed evaluation:
900          [](AudacityProject &)
901          { return Items( wxEmptyString, PopulateEffectsMenu(
902             EffectTypeGenerate,
903             AudioIONotBusyFlag(),
904             AudioIONotBusyFlag())
905          ); }
906       )
907    ) ) };
908    return menu;
909 }
910 
911 static const ReservedCommandFlag
IsRealtimeNotActiveFlag()912 &IsRealtimeNotActiveFlag() { static ReservedCommandFlag flag{
913    [](const AudacityProject &){
914       return !RealtimeEffectManager::Get().RealtimeIsActive();
915    }
916 }; return flag; }  //lll
917 
918 AttachedItem sAttachment1{
919    wxT(""),
920    Shared( GenerateMenu() )
921 };
922 
923 const ReservedCommandFlag&
HasLastEffectFlag()924    HasLastEffectFlag() { static ReservedCommandFlag flag{
925       [](const AudacityProject &project) {
926          return !MenuManager::Get(project).mLastEffect.empty();
927       }
928    }; return flag;
929 }
930 
EffectMenu()931 BaseItemSharedPtr EffectMenu()
932 {
933    // All of this is a bit hacky until we can get more things connected into
934    // the plugin manager...sorry! :-(
935 
936    static BaseItemSharedPtr menu{
937    ( FinderScope{ findCommandHandler },
938    Menu( wxT("Effect"), XXO("Effe&ct"),
939 #ifdef EXPERIMENTAL_EFFECT_MANAGEMENT
940       Section( "Manage",
941          Command( wxT("ManageEffects"), XXO("Add / Remove Plug-ins..."),
942             FN(OnManageEffects), AudioIONotBusyFlag() )
943       ),
944 #endif
945 
946       Section( "RepeatLast",
947          // Delayed evaluation:
948          [](AudacityProject &project)
949          {
950             const auto &lastEffect = MenuManager::Get(project).mLastEffect;
951             TranslatableString buildMenuLabel;
952             if (!lastEffect.empty())
953                buildMenuLabel = XO("Repeat %s")
954                   .Format( EffectManager::Get().GetCommandName(lastEffect) );
955             else
956                buildMenuLabel = XO("Repeat Last Effect");
957 
958             return Command( wxT("RepeatLastEffect"), buildMenuLabel,
959                FN(OnRepeatLastEffect),
960                AudioIONotBusyFlag() | TimeSelectedFlag() |
961                   WaveTracksSelectedFlag() | HasLastEffectFlag(),
962                wxT("Ctrl+R"), findCommandHandler );
963          }
964       ),
965 
966       Section( "Effects",
967          // Delayed evaluation:
968          [](AudacityProject &)
969          { return Items( wxEmptyString, PopulateEffectsMenu(
970             EffectTypeProcess,
971             AudioIONotBusyFlag() | TimeSelectedFlag() | WaveTracksSelectedFlag(),
972             IsRealtimeNotActiveFlag() )
973          ); }
974       )
975    ) ) };
976    return menu;
977 }
978 
979 AttachedItem sAttachment2{
980    wxT(""),
981    Shared( EffectMenu() )
982 };
983 
984 const ReservedCommandFlag&
HasLastAnalyzerFlag()985    HasLastAnalyzerFlag() { static ReservedCommandFlag flag{
986       [](const AudacityProject &project) {
987          if (MenuManager::Get(project).mLastAnalyzerRegistration == MenuCreator::repeattypeunique) return true;
988          return !MenuManager::Get(project).mLastAnalyzer.empty();
989       }
990    }; return flag;
991 }
992 
AnalyzeMenu()993 BaseItemSharedPtr AnalyzeMenu()
994 {
995    // All of this is a bit hacky until we can get more things connected into
996    // the plugin manager...sorry! :-(
997 
998    using Options = CommandManager::Options;
999 
1000    static BaseItemSharedPtr menu{
1001    ( FinderScope{ findCommandHandler },
1002    Menu( wxT("Analyze"), XXO("&Analyze"),
1003 #ifdef EXPERIMENTAL_EFFECT_MANAGEMENT
1004       Section( "Manage",
1005          Command( wxT("ManageAnalyzers"), XXO("Add / Remove Plug-ins..."),
1006             FN(OnManageAnalyzers), AudioIONotBusyFlag() )
1007       ),
1008 #endif
1009 
1010       Section("RepeatLast",
1011          // Delayed evaluation:
1012          [](AudacityProject &project)
1013          {
1014             const auto &lastAnalyzer = MenuManager::Get(project).mLastAnalyzer;
1015             TranslatableString buildMenuLabel;
1016             if (!lastAnalyzer.empty())
1017                buildMenuLabel = XO("Repeat %s")
1018                   .Format(EffectManager::Get().GetCommandName(lastAnalyzer));
1019             else
1020                buildMenuLabel = XO("Repeat Last Analyzer");
1021 
1022             return Command(wxT("RepeatLastAnalyzer"), buildMenuLabel,
1023                FN(OnRepeatLastAnalyzer),
1024                AudioIONotBusyFlag() | TimeSelectedFlag() |
1025                   WaveTracksSelectedFlag() | HasLastAnalyzerFlag(),
1026                Options{}.IsGlobal(), findCommandHandler);
1027          }
1028       ),
1029 
1030       Section( "Analyzers",
1031          Items( "Windows" ),
1032 
1033          // Delayed evaluation:
1034          [](AudacityProject&)
1035          { return Items( wxEmptyString, PopulateEffectsMenu(
1036             EffectTypeAnalyze,
1037             AudioIONotBusyFlag() | TimeSelectedFlag() | WaveTracksSelectedFlag(),
1038             IsRealtimeNotActiveFlag() )
1039          ); }
1040       )
1041    ) ) };
1042    return menu;
1043 }
1044 
1045 AttachedItem sAttachment3{
1046    wxT(""),
1047    Shared( AnalyzeMenu() )
1048 };
1049 
1050 const ReservedCommandFlag&
HasLastToolFlag()1051    HasLastToolFlag() { static ReservedCommandFlag flag{
1052       [](const AudacityProject &project) {
1053       auto& menuManager = MenuManager::Get(project);
1054          if (menuManager.mLastToolRegistration == MenuCreator::repeattypeunique) return true;
1055          return !menuManager.mLastTool.empty();
1056       }
1057    }; return flag;
1058 }
1059 
ToolsMenu()1060 BaseItemSharedPtr ToolsMenu()
1061 {
1062    using Options = CommandManager::Options;
1063 
1064    static BaseItemSharedPtr menu{
1065    ( FinderScope{ findCommandHandler },
1066    Menu( wxT("Tools"), XXO("T&ools"),
1067       Section( "Manage",
1068    #ifdef EXPERIMENTAL_EFFECT_MANAGEMENT
1069          Command( wxT("ManageTools"), XXO("Add / Remove Plug-ins..."),
1070             FN(OnManageTools), AudioIONotBusyFlag() ),
1071 
1072          //Separator(),
1073 
1074    #endif
1075 
1076          Section( "RepeatLast",
1077          // Delayed evaluation:
1078          [](AudacityProject &project)
1079          {
1080             const auto &lastTool = MenuManager::Get(project).mLastTool;
1081             TranslatableString buildMenuLabel;
1082             if (!lastTool.empty())
1083                buildMenuLabel = XO("Repeat %s")
1084                   .Format( EffectManager::Get().GetCommandName(lastTool) );
1085             else
1086                buildMenuLabel = XO("Repeat Last Tool");
1087 
1088             return Command( wxT("RepeatLastTool"), buildMenuLabel,
1089                FN(OnRepeatLastTool),
1090                AudioIONotBusyFlag() |
1091                   HasLastToolFlag(),
1092                Options{}.IsGlobal(), findCommandHandler );
1093          }
1094       ),
1095 
1096       Command( wxT("ManageMacros"), XXO("&Macros..."),
1097             FN(OnManageMacros), AudioIONotBusyFlag() ),
1098 
1099          Menu( wxT("Macros"), XXO("&Apply Macro"),
1100             // Palette has no access key to ensure first letter navigation of
1101             // sub menu
1102             Section( "",
1103                Command( wxT("ApplyMacrosPalette"), XXO("Palette..."),
1104                   FN(OnApplyMacrosPalette), AudioIONotBusyFlag() )
1105             ),
1106 
1107             Section( "",
1108                // Delayed evaluation:
1109                [](AudacityProject&)
1110                { return Items( wxEmptyString, PopulateMacrosMenu( AudioIONotBusyFlag() ) ); }
1111             )
1112          )
1113       ),
1114 
1115       Section( "Other",
1116          Command( wxT("ConfigReset"), XXO("Reset &Configuration"),
1117             FN(OnResetConfig),
1118             AudioIONotBusyFlag() ),
1119 
1120          Command( wxT("FancyScreenshot"), XXO("&Screenshot..."),
1121             FN(OnScreenshot), AudioIONotBusyFlag() ),
1122 
1123    // PRL: team consensus for 2.2.0 was, we let end users have this diagnostic,
1124    // as they used to in 1.3.x
1125    //#ifdef IS_ALPHA
1126          // TODO: What should we do here?  Make benchmark a plug-in?
1127          // Easy enough to do.  We'd call it mod-self-test.
1128          Command( wxT("Benchmark"), XXO("&Run Benchmark..."),
1129             FN(OnBenchmark), AudioIONotBusyFlag() )
1130    //#endif
1131       ),
1132 
1133       Section( "Tools",
1134          // Delayed evaluation:
1135          [](AudacityProject&)
1136          { return Items( wxEmptyString, PopulateEffectsMenu(
1137             EffectTypeTool,
1138             AudioIONotBusyFlag(),
1139             AudioIONotBusyFlag() )
1140          ); }
1141       )
1142 
1143 #ifdef IS_ALPHA
1144       ,
1145       Section( "",
1146          Command( wxT("SimulateRecordingErrors"),
1147             XXO("Simulate Recording Errors"),
1148             FN(OnSimulateRecordingErrors),
1149             AudioIONotBusyFlag(),
1150             Options{}.CheckTest(
1151                [](AudacityProject&){
1152                   return AudioIO::Get()->mSimulateRecordingErrors; } ) ),
1153          Command( wxT("DetectUpstreamDropouts"),
1154             XXO("Detect Upstream Dropouts"),
1155             FN(OnDetectUpstreamDropouts),
1156             AudioIONotBusyFlag(),
1157             Options{}.CheckTest(
1158                [](AudacityProject&){
1159                   return AudioIO::Get()->mDetectUpstreamDropouts; } ) )
1160       )
1161 #endif
1162 
1163 #if defined(IS_ALPHA) || defined(END_USER_JOURNALLING)
1164       ,
1165       Section( "",
1166          Command( wxT("WriteJournal"),
1167             /* i18n-hint a "journal" is a text file that records
1168              the user's interactions with the application */
1169             XXO("Write Journal"),
1170             FN(OnWriteJournal),
1171             AlwaysEnabledFlag,
1172             Options{}.CheckTest( [](AudacityProject&){
1173                return Journal::RecordEnabled(); } ) )
1174       )
1175 #endif
1176 
1177    ) ) };
1178    return menu;
1179 }
1180 
1181 AttachedItem sAttachment4{
1182    wxT(""),
1183    Shared( ToolsMenu() )
1184 };
1185 
ExtraScriptablesIMenu()1186 BaseItemSharedPtr ExtraScriptablesIMenu()
1187 {
1188    // These are the more useful to VI user Scriptables.
1189    static BaseItemSharedPtr menu{
1190    ( FinderScope{ findCommandHandler },
1191    // i18n-hint: Scriptables are commands normally used from Python, Perl etc.
1192    Menu( wxT("Scriptables1"), XXO("Script&ables I"),
1193       // Note that the PLUGIN_SYMBOL must have a space between words,
1194       // whereas the short-form used here must not.
1195       // (So if you did write "CompareAudio" for the PLUGIN_SYMBOL name, then
1196       // you would have to use "Compareaudio" here.)
1197       Command( wxT("SelectTime"), XXO("Select Time..."), FN(OnAudacityCommand),
1198          AudioIONotBusyFlag() ),
1199       Command( wxT("SelectFrequencies"), XXO("Select Frequencies..."),
1200          FN(OnAudacityCommand),
1201          AudioIONotBusyFlag() ),
1202       Command( wxT("SelectTracks"), XXO("Select Tracks..."),
1203          FN(OnAudacityCommand),
1204          AudioIONotBusyFlag() ),
1205       Command( wxT("SetTrackStatus"), XXO("Set Track Status..."),
1206          FN(OnAudacityCommand),
1207          AudioIONotBusyFlag() ),
1208       Command( wxT("SetTrackAudio"), XXO("Set Track Audio..."),
1209          FN(OnAudacityCommand),
1210          AudioIONotBusyFlag() ),
1211       Command( wxT("SetTrackVisuals"), XXO("Set Track Visuals..."),
1212          FN(OnAudacityCommand),
1213          AudioIONotBusyFlag() ),
1214       Command( wxT("GetPreference"), XXO("Get Preference..."),
1215          FN(OnAudacityCommand),
1216          AudioIONotBusyFlag() ),
1217       Command( wxT("SetPreference"), XXO("Set Preference..."),
1218          FN(OnAudacityCommand),
1219          AudioIONotBusyFlag() ),
1220       Command( wxT("SetClip"), XXO("Set Clip..."), FN(OnAudacityCommand),
1221          AudioIONotBusyFlag() ),
1222       Command( wxT("SetEnvelope"), XXO("Set Envelope..."),
1223          FN(OnAudacityCommand),
1224          AudioIONotBusyFlag() ),
1225       Command( wxT("SetLabel"), XXO("Set Label..."), FN(OnAudacityCommand),
1226          AudioIONotBusyFlag() ),
1227       Command( wxT("SetProject"), XXO("Set Project..."), FN(OnAudacityCommand),
1228          AudioIONotBusyFlag() )
1229    ) ) };
1230    return menu;
1231 }
1232 
1233 AttachedItem sAttachment5{
1234    wxT("Optional/Extra/Part2"),
1235    Shared( ExtraScriptablesIMenu() )
1236 };
1237 
ExtraScriptablesIIMenu()1238 BaseItemSharedPtr ExtraScriptablesIIMenu()
1239 {
1240    // Less useful to VI users.
1241    static BaseItemSharedPtr menu{
1242    ( FinderScope{ findCommandHandler },
1243    // i18n-hint: Scriptables are commands normally used from Python, Perl etc.
1244    Menu( wxT("Scriptables2"), XXO("Scripta&bles II"),
1245       Command( wxT("Select"), XXO("Select..."), FN(OnAudacityCommand),
1246          AudioIONotBusyFlag() ),
1247       Command( wxT("SetTrack"), XXO("Set Track..."), FN(OnAudacityCommand),
1248          AudioIONotBusyFlag() ),
1249       Command( wxT("GetInfo"), XXO("Get Info..."), FN(OnAudacityCommand),
1250          AudioIONotBusyFlag() ),
1251       Command( wxT("Message"), XXO("Message..."), FN(OnAudacityCommand),
1252          AudioIONotBusyFlag() ),
1253       Command( wxT("Help"), XXO("Help..."), FN(OnAudacityCommand),
1254          AudioIONotBusyFlag() ),
1255       Command( wxT("Import2"), XXO("Import..."), FN(OnAudacityCommand),
1256          AudioIONotBusyFlag() ),
1257       Command( wxT("Export2"), XXO("Export..."), FN(OnAudacityCommand),
1258          AudioIONotBusyFlag() ),
1259       Command( wxT("OpenProject2"), XXO("Open Project..."),
1260          FN(OnAudacityCommand),
1261          AudioIONotBusyFlag() ),
1262       Command( wxT("SaveProject2"), XXO("Save Project..."),
1263          FN(OnAudacityCommand),
1264          AudioIONotBusyFlag() ),
1265       Command( wxT("Drag"), XXO("Move Mouse..."), FN(OnAudacityCommand),
1266          AudioIONotBusyFlag() ),
1267       Command( wxT("CompareAudio"), XXO("Compare Audio..."),
1268          FN(OnAudacityCommand),
1269          AudioIONotBusyFlag() ),
1270       // i18n-hint: Screenshot in the help menu has a much bigger dialog.
1271       Command( wxT("Screenshot"), XXO("Screenshot (short format)..."),
1272          FN(OnAudacityCommand),
1273          AudioIONotBusyFlag() )
1274    ) ) };
1275    return menu;
1276 }
1277 
1278 AttachedItem sAttachment6{
1279    wxT("Optional/Extra/Part2"),
1280    Shared( ExtraScriptablesIIMenu() )
1281 };
1282 
1283 }
1284 
1285 #undef FN
1286