1 /**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 Menus.cpp
6
7 Dominic Mazzoni
8 Brian Gunlogson
9 et al.
10
11 *******************************************************************//**
12
13 \file Menus.cpp
14 \brief Functions for building toobar menus and enabling and disabling items
15
16 *//****************************************************************//**
17
18 \class MenuCreator
19 \brief MenuCreator is responsible for creating the main menu bar.
20
21 *//****************************************************************//**
22
23 \class MenuManager
24 \brief MenuManager handles updates to menu state.
25
26 *//*******************************************************************/
27
28
29 #include "Menus.h"
30
31
32
33 #include <wx/frame.h>
34
35 #include "Project.h"
36 #include "ProjectHistory.h"
37 #include "ProjectSettings.h"
38 #include "ProjectWindows.h"
39 #include "UndoManager.h"
40 #include "commands/CommandManager.h"
41 #include "toolbars/ToolManager.h"
42 #include "widgets/AudacityMessageBox.h"
43 #include "BasicUI.h"
44
45 #include <unordered_set>
46
47 #include <wx/menu.h>
48 #include <wx/windowptr.h>
49
MenuCreator()50 MenuCreator::MenuCreator()
51 {
52 mLastAnalyzerRegistration = repeattypenone;
53 mLastToolRegistration = repeattypenone;
54
55 mRepeatGeneratorFlags = 0;
56 mRepeatEffectFlags = 0;
57 mRepeatAnalyzerFlags = 0;
58 mRepeatToolFlags = 0;
59 }
60
~MenuCreator()61 MenuCreator::~MenuCreator()
62 {
63 }
64
65 static const AudacityProject::AttachedObjects::RegisteredFactory key{
__anon35effb820102( )66 []( AudacityProject &project ){
67 return std::make_shared< MenuManager >( project ); }
68 };
69
Get(AudacityProject & project)70 MenuManager &MenuManager::Get( AudacityProject &project )
71 {
72 return project.AttachedObjects::Get< MenuManager >( key );
73 }
74
Get(const AudacityProject & project)75 const MenuManager &MenuManager::Get( const AudacityProject &project )
76 {
77 return Get( const_cast< AudacityProject & >( project ) );
78 }
79
MenuManager(AudacityProject & project)80 MenuManager::MenuManager( AudacityProject &project )
81 : mProject{ project }
82 {
83 UpdatePrefs();
84 mProject.Bind( EVT_UNDO_OR_REDO, &MenuManager::OnUndoRedo, this );
85 mProject.Bind( EVT_UNDO_RESET, &MenuManager::OnUndoRedo, this );
86 mProject.Bind( EVT_UNDO_PUSHED, &MenuManager::OnUndoRedo, this );
87 mProject.Bind( EVT_UNDO_RENAMED, &MenuManager::OnUndoRedo, this );
88 }
89
~MenuManager()90 MenuManager::~MenuManager()
91 {
92 mProject.Unbind( EVT_UNDO_OR_REDO, &MenuManager::OnUndoRedo, this );
93 mProject.Unbind( EVT_UNDO_RESET, &MenuManager::OnUndoRedo, this );
94 mProject.Unbind( EVT_UNDO_PUSHED, &MenuManager::OnUndoRedo, this );
95 }
96
UpdatePrefs()97 void MenuManager::UpdatePrefs()
98 {
99 bool bSelectAllIfNone;
100 gPrefs->Read(wxT("/GUI/SelectAllOnNone"), &bSelectAllIfNone, false);
101 // 0 is grey out, 1 is Autoselect, 2 is Give warnings.
102 #ifdef EXPERIMENTAL_DA
103 // DA warns or greys out.
104 mWhatIfNoSelection = bSelectAllIfNone ? 2 : 0;
105 #else
106 // Audacity autoselects or warns.
107 mWhatIfNoSelection = bSelectAllIfNone ? 1 : 2;
108 #endif
109 mStopIfWasPaused = true; // not configurable for now, but could be later.
110 }
111
BeginGroup(Registry::GroupItem & item,const Path & path)112 void MenuVisitor::BeginGroup( Registry::GroupItem &item, const Path &path )
113 {
114 bool isMenu = false;
115 bool isExtension = false;
116 auto pItem = &item;
117 if ( pItem->Transparent() ) {
118 }
119 else if ( dynamic_cast<MenuTable::MenuSection*>( pItem ) ) {
120 if ( !needSeparator.empty() )
121 needSeparator.back() = true;
122 }
123 else if ( auto pWhole = dynamic_cast<MenuTable::WholeMenu*>( pItem ) ) {
124 isMenu = true;
125 isExtension = pWhole->extension;
126 MaybeDoSeparator();
127 }
128
129 DoBeginGroup( item, path );
130
131 if ( isMenu ) {
132 needSeparator.push_back( false );
133 firstItem.push_back( !isExtension );
134 }
135 }
136
EndGroup(Registry::GroupItem & item,const Path & path)137 void MenuVisitor::EndGroup( Registry::GroupItem &item, const Path &path )
138 {
139 auto pItem = &item;
140 if ( pItem->Transparent() ) {
141 }
142 else if ( dynamic_cast<MenuTable::MenuSection*>( pItem ) ) {
143 if ( !needSeparator.empty() )
144 needSeparator.back() = true;
145 }
146 else if ( dynamic_cast<MenuTable::WholeMenu*>( pItem ) ) {
147 firstItem.pop_back();
148 needSeparator.pop_back();
149 }
150
151 DoEndGroup( item, path );
152 }
153
Visit(Registry::SingleItem & item,const Path & path)154 void MenuVisitor::Visit( Registry::SingleItem &item, const Path &path )
155 {
156 MaybeDoSeparator();
157 DoVisit( item, path );
158 }
159
MaybeDoSeparator()160 void MenuVisitor::MaybeDoSeparator()
161 {
162 bool separate = false;
163 if ( !needSeparator.empty() ) {
164 separate = needSeparator.back() && !firstItem.back();
165 needSeparator.back() = false;
166 firstItem.back() = false;
167 }
168
169 if ( separate )
170 DoSeparator();
171 }
172
DoBeginGroup(Registry::GroupItem &,const Path &)173 void MenuVisitor::DoBeginGroup( Registry::GroupItem &, const Path & )
174 {
175 }
176
DoEndGroup(Registry::GroupItem &,const Path &)177 void MenuVisitor::DoEndGroup( Registry::GroupItem &, const Path & )
178 {
179 }
180
DoVisit(Registry::SingleItem &,const Path &)181 void MenuVisitor::DoVisit( Registry::SingleItem &, const Path & )
182 {
183 }
184
DoSeparator()185 void MenuVisitor::DoSeparator()
186 {
187 }
188
189 namespace MenuTable {
190
MenuItem(const Identifier & internalName,const TranslatableString & title_,BaseItemPtrs && items_)191 MenuItem::MenuItem( const Identifier &internalName,
192 const TranslatableString &title_, BaseItemPtrs &&items_ )
193 : ConcreteGroupItem< false, ToolbarMenuVisitor >{
194 internalName, std::move( items_ ) }, title{ title_ }
195 {
196 wxASSERT( !title.empty() );
197 }
~MenuItem()198 MenuItem::~MenuItem() {}
199
ConditionalGroupItem(const Identifier & internalName,Condition condition_,BaseItemPtrs && items_)200 ConditionalGroupItem::ConditionalGroupItem(
201 const Identifier &internalName, Condition condition_, BaseItemPtrs &&items_ )
202 : ConcreteGroupItem< false, ToolbarMenuVisitor >{
203 internalName, std::move( items_ ) }, condition{ condition_ }
204 {
205 }
~ConditionalGroupItem()206 ConditionalGroupItem::~ConditionalGroupItem() {}
207
CommandItem(const CommandID & name_,const TranslatableString & label_in_,CommandFunctorPointer callback_,CommandFlag flags_,const CommandManager::Options & options_,CommandHandlerFinder finder_)208 CommandItem::CommandItem(const CommandID &name_,
209 const TranslatableString &label_in_,
210 CommandFunctorPointer callback_,
211 CommandFlag flags_,
212 const CommandManager::Options &options_,
213 CommandHandlerFinder finder_)
214 : SingleItem{ name_ }, label_in{ label_in_ }
215 , finder{ finder_ }, callback{ callback_ }
216 , flags{ flags_ }, options{ options_ }
217 {}
~CommandItem()218 CommandItem::~CommandItem() {}
219
CommandGroupItem(const Identifier & name_,std::vector<ComponentInterfaceSymbol> items_,CommandFunctorPointer callback_,CommandFlag flags_,bool isEffect_,CommandHandlerFinder finder_)220 CommandGroupItem::CommandGroupItem(const Identifier &name_,
221 std::vector< ComponentInterfaceSymbol > items_,
222 CommandFunctorPointer callback_,
223 CommandFlag flags_,
224 bool isEffect_,
225 CommandHandlerFinder finder_)
226 : SingleItem{ name_ }, items{ std::move(items_) }
227 , finder{ finder_ }, callback{ callback_ }
228 , flags{ flags_ }, isEffect{ isEffect_ }
229 {}
~CommandGroupItem()230 CommandGroupItem::~CommandGroupItem() {}
231
~SpecialItem()232 SpecialItem::~SpecialItem() {}
233
~MenuSection()234 MenuSection::~MenuSection() {}
~WholeMenu()235 WholeMenu::~WholeMenu() {}
236
237 CommandHandlerFinder FinderScope::sFinder =
__anon35effb820202(AudacityProject &project) 238 [](AudacityProject &project) -> CommandHandlerObject & {
239 // If this default finder function is reached, then FinderScope should
240 // have been used somewhere, or an explicit CommandHandlerFinder passed
241 // to menu item constructors
242 wxASSERT( false );
243 return project;
244 };
245
246 }
247
248 /// CreateMenusAndCommands builds the menus, and also rebuilds them after
249 /// changes in configured preferences - for example changes in key-bindings
250 /// affect the short-cut key legend that appears beside each command,
251
252 namespace {
253
254 using namespace Registry;
255
256 const auto MenuPathStart = wxT("MenuBar");
257
sRegistry()258 static Registry::GroupItem &sRegistry()
259 {
260 static Registry::TransparentGroupItem<> registry{ MenuPathStart };
261 return registry;
262 }
263 }
264
AttachedItem(const Placement & placement,BaseItemPtr pItem)265 MenuTable::AttachedItem::AttachedItem(
266 const Placement &placement, BaseItemPtr pItem )
267 {
268 Registry::RegisterItem( sRegistry(), placement, std::move( pItem ) );
269 }
270
DestroyRegistry()271 void MenuTable::DestroyRegistry()
272 {
273 sRegistry().items.clear();
274 }
275
276 namespace {
277
278 using namespace MenuTable;
279
280 struct MenuItemVisitor : ToolbarMenuVisitor
281 {
MenuItemVisitor__anon35effb820411::MenuItemVisitor282 MenuItemVisitor( AudacityProject &proj, CommandManager &man )
283 : ToolbarMenuVisitor(proj), manager( man ) {}
284
DoBeginGroup__anon35effb820411::MenuItemVisitor285 void DoBeginGroup( GroupItem &item, const Path& ) override
286 {
287 auto pItem = &item;
288 if (const auto pMenu =
289 dynamic_cast<MenuItem*>( pItem )) {
290 manager.BeginMenu( pMenu->title );
291 }
292 else
293 if (const auto pConditionalGroup =
294 dynamic_cast<ConditionalGroupItem*>( pItem )) {
295 const auto flag = pConditionalGroup->condition();
296 if (!flag)
297 manager.BeginOccultCommands();
298 // to avoid repeated call of condition predicate in EndGroup():
299 flags.push_back(flag);
300 }
301 else
302 if ( pItem->Transparent() ) {
303 }
304 else
305 if ( const auto pGroup = dynamic_cast<MenuSection*>( pItem ) ) {
306 }
307 else
308 wxASSERT( false );
309 }
310
DoEndGroup__anon35effb820411::MenuItemVisitor311 void DoEndGroup( GroupItem &item, const Path& ) override
312 {
313 auto pItem = &item;
314 if (const auto pMenu =
315 dynamic_cast<MenuItem*>( pItem )) {
316 manager.EndMenu();
317 }
318 else
319 if (const auto pConditionalGroup =
320 dynamic_cast<ConditionalGroupItem*>( pItem )) {
321 const bool flag = flags.back();
322 if (!flag)
323 manager.EndOccultCommands();
324 flags.pop_back();
325 }
326 else
327 if ( pItem->Transparent() ) {
328 }
329 else
330 if ( const auto pGroup = dynamic_cast<MenuSection*>( pItem ) ) {
331 }
332 else
333 wxASSERT( false );
334 }
335
DoVisit__anon35effb820411::MenuItemVisitor336 void DoVisit( SingleItem &item, const Path& ) override
337 {
338 const auto pCurrentMenu = manager.CurrentMenu();
339 if ( !pCurrentMenu ) {
340 // There may have been a mistake in the placement hint that registered
341 // this single item. It's not within any menu.
342 wxASSERT( false );
343 return;
344 }
345 auto pItem = &item;
346 if (const auto pCommand =
347 dynamic_cast<CommandItem*>( pItem )) {
348 manager.AddItem( project,
349 pCommand->name, pCommand->label_in,
350 pCommand->finder, pCommand->callback,
351 pCommand->flags, pCommand->options
352 );
353 }
354 else
355 if (const auto pCommandList =
356 dynamic_cast<CommandGroupItem*>( pItem ) ) {
357 manager.AddItemList(pCommandList->name,
358 pCommandList->items.data(), pCommandList->items.size(),
359 pCommandList->finder, pCommandList->callback,
360 pCommandList->flags, pCommandList->isEffect);
361 }
362 else
363 if (const auto pSpecial =
364 dynamic_cast<SpecialItem*>( pItem )) {
365 wxASSERT( pCurrentMenu );
366 pSpecial->fn( project, *pCurrentMenu );
367 }
368 else
369 wxASSERT( false );
370 }
371
DoSeparator__anon35effb820411::MenuItemVisitor372 void DoSeparator() override
373 {
374 manager.AddSeparator();
375 }
376
377 CommandManager &manager;
378 std::vector<bool> flags;
379 };
380 }
381
CreateMenusAndCommands(AudacityProject & project)382 void MenuCreator::CreateMenusAndCommands(AudacityProject &project)
383 {
384 // Once only, cause initial population of preferences for the ordering
385 // of some menu items that used to be given in tables but are now separately
386 // registered in several .cpp files; the sequence of registration depends
387 // on unspecified accidents of static initialization order across
388 // compilation units, so we need something specific here to preserve old
389 // default appearance of menus.
390 // But this needs only to mention some strings -- there is no compilation or
391 // link dependency of this source file on those other implementation files.
392 static Registry::OrderingPreferenceInitializer init{
393 MenuPathStart,
394 {
395 {wxT(""), wxT(
396 "File,Edit,Select,View,Transport,Tracks,Generate,Effect,Analyze,Tools,Window,Optional,Help"
397 )},
398 {wxT("/Optional/Extra/Part1"), wxT(
399 "Transport,Tools,Mixer,Edit,PlayAtSpeed,Seek,Device,Select"
400 )},
401 {wxT("/Optional/Extra/Part2"), wxT(
402 "Navigation,Focus,Cursor,Track,Scriptables1,Scriptables2"
403 )},
404 {wxT("/View/Windows"), wxT("UndoHistory,Karaoke,MixerBoard")},
405 {wxT("/Analyze/Analyzers/Windows"), wxT("ContrastAnalyser,PlotSpectrum")},
406 {wxT("/Transport/Basic"), wxT("Play,Record,Scrubbing,Cursor")},
407 {wxT("/View/Other/Toolbars/Toolbars/Other"), wxT(
408 "ShowTransportTB,ShowToolsTB,ShowRecordMeterTB,ShowPlayMeterTB,"
409 //"ShowMeterTB,"
410 "ShowMixerTB,"
411 "ShowEditTB,ShowTranscriptionTB,ShowScrubbingTB,ShowDeviceTB,ShowSelectionTB,"
412 "ShowSpectralSelectionTB") }
413 }
414 };
415
416 auto &commandManager = CommandManager::Get( project );
417
418 // The list of defaults to exclude depends on
419 // preference wxT("/GUI/Shortcuts/FullDefaults"), which may have changed.
420 commandManager.SetMaxList();
421
422 auto menubar = commandManager.AddMenuBar(wxT("appmenu"));
423 wxASSERT(menubar);
424
425 MenuItemVisitor visitor{ project, commandManager };
426 MenuManager::Visit( visitor );
427
428 GetProjectFrame( project ).SetMenuBar(menubar.release());
429
430 mLastFlags = AlwaysEnabledFlag;
431
432 #if defined(_DEBUG)
433 // c->CheckDups();
434 #endif
435 }
436
Visit(ToolbarMenuVisitor & visitor)437 void MenuManager::Visit( ToolbarMenuVisitor &visitor )
438 {
439 static const auto menuTree = MenuTable::Items( MenuPathStart );
440 Registry::Visit( visitor, menuTree.get(), &sRegistry() );
441 }
442
443 // TODO: This surely belongs in CommandManager?
ModifyUndoMenuItems(AudacityProject & project)444 void MenuManager::ModifyUndoMenuItems(AudacityProject &project)
445 {
446 TranslatableString desc;
447 auto &undoManager = UndoManager::Get( project );
448 auto &commandManager = CommandManager::Get( project );
449 int cur = undoManager.GetCurrentState();
450
451 if (undoManager.UndoAvailable()) {
452 undoManager.GetShortDescription(cur, &desc);
453 commandManager.Modify(wxT("Undo"),
454 XXO("&Undo %s")
455 .Format( desc ));
456 commandManager.Enable(wxT("Undo"),
457 ProjectHistory::Get( project ).UndoAvailable());
458 }
459 else {
460 commandManager.Modify(wxT("Undo"),
461 XXO("&Undo"));
462 }
463
464 if (undoManager.RedoAvailable()) {
465 undoManager.GetShortDescription(cur+1, &desc);
466 commandManager.Modify(wxT("Redo"),
467 XXO("&Redo %s")
468 .Format( desc ));
469 commandManager.Enable(wxT("Redo"),
470 ProjectHistory::Get( project ).RedoAvailable());
471 }
472 else {
473 commandManager.Modify(wxT("Redo"),
474 XXO("&Redo"));
475 commandManager.Enable(wxT("Redo"), false);
476 }
477 }
478
479 // Get hackcess to a protected method
480 class wxFrameEx : public wxFrame
481 {
482 public:
483 using wxFrame::DetachMenuBar;
484 };
485
RebuildMenuBar(AudacityProject & project)486 void MenuCreator::RebuildMenuBar(AudacityProject &project)
487 {
488 // On OSX, we can't rebuild the menus while a modal dialog is being shown
489 // since the enabled state for menus like Quit and Preference gets out of
490 // sync with wxWidgets idea of what it should be.
491 #if defined(__WXMAC__) && defined(_DEBUG)
492 {
493 wxDialog *dlg =
494 wxDynamicCast(wxGetTopLevelParent(wxWindow::FindFocus()), wxDialog);
495 wxASSERT((!dlg || !dlg->IsModal()));
496 }
497 #endif
498
499 // Delete the menus, since we will soon recreate them.
500 // Rather oddly, the menus don't vanish as a result of doing this.
501 {
502 auto &window = static_cast<wxFrameEx&>( GetProjectFrame( project ) );
503 wxWindowPtr<wxMenuBar> menuBar{ window.GetMenuBar() };
504 window.DetachMenuBar();
505 // menuBar gets deleted here
506 }
507
508 CommandManager::Get( project ).PurgeData();
509
510 CreateMenusAndCommands(project);
511 }
512
OnUndoRedo(wxCommandEvent & evt)513 void MenuManager::OnUndoRedo( wxCommandEvent &evt )
514 {
515 evt.Skip();
516 ModifyUndoMenuItems( mProject );
517 UpdateMenus();
518 }
519
520 namespace{
521 using Predicates = std::vector< ReservedCommandFlag::Predicate >;
RegisteredPredicates()522 Predicates &RegisteredPredicates()
523 {
524 static Predicates thePredicates;
525 return thePredicates;
526 }
Options()527 std::vector< CommandFlagOptions > &Options()
528 {
529 static std::vector< CommandFlagOptions > options;
530 return options;
531 }
532 }
533
ReservedCommandFlag(const Predicate & predicate,const CommandFlagOptions & options)534 ReservedCommandFlag::ReservedCommandFlag(
535 const Predicate &predicate, const CommandFlagOptions &options )
536 {
537 static size_t sNextReservedFlag = 0;
538 // This will throw std::out_of_range if the constant NCommandFlags is too
539 // small
540 set( sNextReservedFlag++ );
541 RegisteredPredicates().emplace_back( predicate );
542 Options().emplace_back( options );
543 }
544
GetUpdateFlags(bool checkActive) const545 CommandFlag MenuManager::GetUpdateFlags( bool checkActive ) const
546 {
547 // This method determines all of the flags that determine whether
548 // certain menu items and commands should be enabled or disabled,
549 // and returns them in a bitfield. Note that if none of the flags
550 // have changed, it's not necessary to even check for updates.
551
552 // static variable, used to remember flags for next time.
553 static CommandFlag lastFlags;
554
555 CommandFlag flags, quickFlags;
556
557 const auto &options = Options();
558 size_t ii = 0;
559 for ( const auto &predicate : RegisteredPredicates() ) {
560 if ( options[ii].quickTest ) {
561 quickFlags[ii] = true;
562 if( predicate( mProject ) )
563 flags[ii] = true;
564 }
565 ++ii;
566 }
567
568 if ( checkActive && !GetProjectFrame( mProject ).IsActive() )
569 // quick 'short-circuit' return.
570 flags = (lastFlags & ~quickFlags) | flags;
571 else {
572 ii = 0;
573 for ( const auto &predicate : RegisteredPredicates() ) {
574 if ( !options[ii].quickTest && predicate( mProject ) )
575 flags[ii] = true;
576 ++ii;
577 }
578 }
579
580 lastFlags = flags;
581 return flags;
582 }
583
ModifyAllProjectToolbarMenus()584 void MenuManager::ModifyAllProjectToolbarMenus()
585 {
586 for (auto pProject : AllProjects{}) {
587 auto &project = *pProject;
588 MenuManager::Get(project).ModifyToolbarMenus(project);
589 }
590 }
591
ModifyToolbarMenus(AudacityProject & project)592 void MenuManager::ModifyToolbarMenus(AudacityProject &project)
593 {
594 // Refreshes can occur during shutdown and the toolmanager may already
595 // be deleted, so protect against it.
596 auto &toolManager = ToolManager::Get( project );
597
598 auto &settings = ProjectSettings::Get( project );
599
600 // Now, go through each toolbar, and call EnableDisableButtons()
601 for (int i = 0; i < ToolBarCount; i++) {
602 auto bar = toolManager.GetToolBar(i);
603 if (bar)
604 bar->EnableDisableButtons();
605 }
606
607 // These don't really belong here, but it's easier and especially so for
608 // the Edit toolbar and the sync-lock menu item.
609 bool active;
610
611 gPrefs->Read(wxT("/GUI/SyncLockTracks"), &active, false);
612 settings.SetSyncLock(active);
613
614 CommandManager::Get( project ).UpdateCheckmarks( project );
615 }
616
617 namespace
618 {
619 using MenuItemEnablers = std::vector<MenuItemEnabler>;
Enablers()620 MenuItemEnablers &Enablers()
621 {
622 static MenuItemEnablers enablers;
623 return enablers;
624 }
625 }
626
RegisteredMenuItemEnabler(const MenuItemEnabler & enabler)627 RegisteredMenuItemEnabler::RegisteredMenuItemEnabler(
628 const MenuItemEnabler &enabler )
629 {
630 Enablers().emplace_back( enabler );
631 }
632
633 // checkActive is a temporary hack that should be removed as soon as we
634 // get multiple effect preview working
UpdateMenus(bool checkActive)635 void MenuManager::UpdateMenus( bool checkActive )
636 {
637 auto &project = mProject;
638
639 auto flags = GetUpdateFlags(checkActive);
640 // Return from this function if nothing's changed since
641 // the last time we were here.
642 if (flags == mLastFlags)
643 return;
644 mLastFlags = flags;
645
646 auto flags2 = flags;
647
648 // We can enable some extra items if we have select-all-on-none.
649 //EXPLAIN-ME: Why is this here rather than in GetUpdateFlags()?
650 //ANSWER: Because flags2 is used in the menu enable/disable.
651 //The effect still needs flags to determine whether it will need
652 //to actually do the 'select all' to make the command valid.
653
654 for ( const auto &enabler : Enablers() ) {
655 auto actual = enabler.actualFlags();
656 if (
657 enabler.applicable( project ) && (flags & actual) == actual
658 )
659 flags2 |= enabler.possibleFlags();
660 }
661
662 auto &commandManager = CommandManager::Get( project );
663
664 // With select-all-on-none, some items that we don't want enabled may have
665 // been enabled, since we changed the flags. Here we manually disable them.
666 // 0 is grey out, 1 is Autoselect, 2 is Give warnings.
667 commandManager.EnableUsingFlags(
668 flags2, // the "lax" flags
669 (mWhatIfNoSelection == 0 ? flags2 : flags) // the "strict" flags
670 );
671
672 MenuManager::ModifyToolbarMenus(project);
673 }
674
675 /// The following method moves to the previous track
676 /// selecting and unselecting depending if you are on the start of a
677 /// block or not.
678
RebuildAllMenuBars()679 void MenuCreator::RebuildAllMenuBars()
680 {
681 for( auto p : AllProjects{} ) {
682 MenuManager::Get(*p).RebuildMenuBar(*p);
683 #if defined(__WXGTK__)
684 // Workaround for:
685 //
686 // http://bugzilla.audacityteam.org/show_bug.cgi?id=458
687 //
688 // This workaround should be removed when Audacity updates to wxWidgets 3.x which has a fix.
689 auto &window = GetProjectFrame( *p );
690 wxRect r = window.GetRect();
691 window.SetSize(wxSize(1,1));
692 window.SetSize(r.GetSize());
693 #endif
694 }
695 }
696
ReportIfActionNotAllowed(const TranslatableString & Name,CommandFlag & flags,CommandFlag flagsRqd)697 bool MenuManager::ReportIfActionNotAllowed(
698 const TranslatableString & Name, CommandFlag & flags, CommandFlag flagsRqd )
699 {
700 auto &project = mProject;
701 bool bAllowed = TryToMakeActionAllowed( flags, flagsRqd );
702 if( bAllowed )
703 return true;
704 auto &cm = CommandManager::Get( project );
705 TellUserWhyDisallowed( Name, flags & flagsRqd, flagsRqd);
706 return false;
707 }
708
709 /// Determines if flags for command are compatible with current state.
710 /// If not, then try some recovery action to make it so.
711 /// @return whether compatible or not after any actions taken.
TryToMakeActionAllowed(CommandFlag & flags,CommandFlag flagsRqd)712 bool MenuManager::TryToMakeActionAllowed(
713 CommandFlag & flags, CommandFlag flagsRqd )
714 {
715 auto &project = mProject;
716
717 if( flags.none() )
718 flags = GetUpdateFlags();
719
720 // Visit the table of recovery actions
721 auto &enablers = Enablers();
722 auto iter = enablers.begin(), end = enablers.end();
723 while ((flags & flagsRqd) != flagsRqd && iter != end) {
724 const auto &enabler = *iter;
725 auto actual = enabler.actualFlags();
726 auto MissingFlags = (~flags & flagsRqd);
727 if (
728 // Do we have the right precondition?
729 (flags & actual) == actual
730 &&
731 // Can we get the condition we need?
732 (MissingFlags & enabler.possibleFlags()).any()
733 ) {
734 // Then try the function
735 enabler.tryEnable( project, flagsRqd );
736 flags = GetUpdateFlags();
737 }
738 ++iter;
739 }
740 return (flags & flagsRqd) == flagsRqd;
741 }
742
TellUserWhyDisallowed(const TranslatableString & Name,CommandFlag flagsGot,CommandFlag flagsRequired)743 void MenuManager::TellUserWhyDisallowed(
744 const TranslatableString & Name, CommandFlag flagsGot, CommandFlag flagsRequired )
745 {
746 // The default string for 'reason' is a catch all. I hope it won't ever be seen
747 // and that we will get something more specific.
748 auto reason = XO("There was a problem with your last action. If you think\nthis is a bug, please tell us exactly where it occurred.");
749 // The default title string is 'Disallowed'.
750 auto untranslatedTitle = XO("Disallowed");
751 wxString helpPage;
752
753 bool enableDefaultMessage = true;
754 bool defaultMessage = true;
755
756 auto doOption = [&](const CommandFlagOptions &options) {
757 if ( options.message ) {
758 reason = options.message( Name );
759 defaultMessage = false;
760 if ( !options.title.empty() )
761 untranslatedTitle = options.title;
762 helpPage = options.helpPage;
763 return true;
764 }
765 else {
766 enableDefaultMessage =
767 enableDefaultMessage && options.enableDefaultMessage;
768 return false;
769 }
770 };
771
772 const auto &alloptions = Options();
773 auto missingFlags = flagsRequired & ~flagsGot;
774
775 // Find greatest priority
776 unsigned priority = 0;
777 for ( const auto &options : alloptions )
778 priority = std::max( priority, options.priority );
779
780 // Visit all unsatisfied conditions' options, by descending priority,
781 // stopping when we find a message
782 ++priority;
783 while( priority-- ) {
784 size_t ii = 0;
785 for ( const auto &options : alloptions ) {
786 if (
787 priority == options.priority
788 &&
789 missingFlags[ii]
790 &&
791 doOption( options ) )
792 goto done;
793
794 ++ii;
795 }
796 }
797 done:
798
799 if (
800 // didn't find a message
801 defaultMessage
802 &&
803 // did find a condition that suppresses the default message
804 !enableDefaultMessage
805 )
806 return;
807
808 // Does not have the warning icon...
809 BasicUI::ShowErrorDialog( {},
810 untranslatedTitle,
811 reason,
812 helpPage);
813 }
814