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