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