diff --git a/src/AudacityApp.cpp b/src/AudacityApp.cpp index 4abc5c898..68c8c0d6f 100644 --- a/src/AudacityApp.cpp +++ b/src/AudacityApp.cpp @@ -109,10 +109,6 @@ It handles initialization and termination by subclassing wxApp. #include "tracks/ui/Scrubbing.h" #include "widgets/FileHistory.h" -#if defined(__WXMAC__) -#include "menus/WindowMenus.h" -#endif - #ifdef EXPERIMENTAL_EASY_CHANGE_KEY_BINDINGS #include "prefs/KeyConfigPrefs.h" #endif @@ -889,11 +885,6 @@ BEGIN_EVENT_TABLE(AudacityApp, wxApp) EVT_MENU_RANGE(FileHistory::ID_RECENT_FIRST, FileHistory::ID_RECENT_LAST, AudacityApp::OnMRUFile) -#if defined(__WXMAC__) - // Window menu event handlers. - EVT_MENU_RANGE(WindowActions::ID_BASE, WindowActions::ID_LAST, AudacityApp::OnWindow) -#endif - // Handle AppCommandEvents (usually from a script) EVT_APP_COMMAND(wxID_ANY, AudacityApp::OnReceiveCommand) @@ -987,35 +978,6 @@ void AudacityApp::OnMRUFile(wxCommandEvent& event) { history.RemoveFileFromHistory(n); } -#if defined(__WXMAC__) -// Handles switching projects when an item in the Window menu is selected -void AudacityApp::OnWindow(wxCommandEvent& event) -{ - // Get the project's number - int projectNumber = event.GetId() - WindowActions::ID_BASE; - - // Search for it - for (auto project : AllProjects{}) - { - if (project->GetProjectNumber() == projectNumber) - { - // Make it the active project - SetActiveProject(project.get()); - - // And ensure it's visible - wxFrame *frame = project->GetFrame(); - if (frame->IsIconized()) - { - frame->Restore(); - } - frame->Raise(); - - break; - } - } -} -#endif - void AudacityApp::OnTimer(wxTimerEvent& WXUNUSED(event)) { // Filenames are queued when Audacity receives a few of the diff --git a/src/AudacityApp.h b/src/AudacityApp.h index a75cf306a..6886d1d94 100644 --- a/src/AudacityApp.h +++ b/src/AudacityApp.h @@ -69,10 +69,6 @@ class AudacityApp final : public wxApp { // A wrapper of the above that does not throw bool SafeMRUOpen(const wxString &fileName); -#if defined(__WXMAC__) - void OnWindow(wxCommandEvent &event); -#endif - void OnReceiveCommand(AppCommandEvent &event); void OnKeyDown(wxKeyEvent &event); diff --git a/src/Project.cpp b/src/Project.cpp index fa4f4f2bd..5b4ffb128 100644 --- a/src/Project.cpp +++ b/src/Project.cpp @@ -15,14 +15,11 @@ #include "KeyboardCapture.h" #include "ondemand/ODTaskThread.h" -#if defined(__WXMAC__) -#include "menus/WindowMenus.h" -#endif - #include #include wxDEFINE_EVENT(EVT_TRACK_PANEL_TIMER, wxCommandEvent); +wxDEFINE_EVENT(EVT_PROJECT_ACTIVATION, wxCommandEvent); size_t AllProjects::size() const { @@ -115,13 +112,9 @@ void SetActiveProject(AudacityProject * project) if ( gActiveProject != project ) { gActiveProject = project; KeyboardCapture::Capture( nullptr ); + wxTheApp->QueueEvent( safenew wxCommandEvent{ EVT_PROJECT_ACTIVATION } ); } wxTheApp->SetTopWindow( FindProjectFrame( project ) ); - -#if defined(__WXMAC__) - // Refresh the Window menu - WindowActions::Refresh(); -#endif } AudacityProject::AudacityProject() diff --git a/src/Project.h b/src/Project.h index f5122728c..4899dca82 100644 --- a/src/Project.h +++ b/src/Project.h @@ -93,6 +93,11 @@ using AttachedWindows = ClientData::Site< wxDECLARE_EXPORTED_EVENT(AUDACITY_DLL_API, EVT_TRACK_PANEL_TIMER, wxCommandEvent); +// This event is emitted by the application object when there is a change +// in the activated project +wxDECLARE_EXPORTED_EVENT(AUDACITY_DLL_API, + EVT_PROJECT_ACTIVATION, wxCommandEvent); + ///\brief The top-level handle to an Audacity project. It serves as a source /// of events that other objects can bind to, and a container of associated /// sub-objects that it treats opaquely. It stores a filename and a status diff --git a/src/ProjectFileIO.cpp b/src/ProjectFileIO.cpp index adb7a3509..eaeda687f 100644 --- a/src/ProjectFileIO.cpp +++ b/src/ProjectFileIO.cpp @@ -24,9 +24,7 @@ Paul Licameli split from AudacityProject.cpp #include "widgets/AudacityMessageBox.h" #include "widgets/NumericTextCtrl.h" -#if defined(__WXMAC__) -#include "menus/WindowMenus.h" -#endif +wxDEFINE_EVENT(EVT_PROJECT_TITLE_CHANGE, wxCommandEvent); static void RefreshAllTitles(bool bShowProjectNumbers ) { @@ -134,13 +132,13 @@ void ProjectFileIO::SetProjectTitle( int number) name += _("(Recovered)"); } - window.SetTitle( name ); - window.SetName(name); // to make the nvda screen reader read the correct title + if ( name != window.GetTitle() ) { + window.SetTitle( name ); + window.SetName(name); // to make the nvda screen reader read the correct title -#if defined(__WXMAC__) - // Refresh the Window menu - WindowActions::Refresh(); -#endif + project.QueueEvent( + safenew wxCommandEvent{ EVT_PROJECT_TITLE_CHANGE } ); + } } // Most of this string was duplicated 3 places. Made the warning consistent in this global. diff --git a/src/ProjectFileIO.h b/src/ProjectFileIO.h index c9c3c2b9a..d5be00f26 100644 --- a/src/ProjectFileIO.h +++ b/src/ProjectFileIO.h @@ -96,4 +96,9 @@ public: size_t UnnamedCount; }; +// This event is emitted by the project when there is a change +// in its title +wxDECLARE_EXPORTED_EVENT(AUDACITY_DLL_API, + EVT_PROJECT_TITLE_CHANGE, wxCommandEvent); + #endif diff --git a/src/menus/WindowMenus.cpp b/src/menus/WindowMenus.cpp index 2ee23c1db..a40c0009a 100644 --- a/src/menus/WindowMenus.cpp +++ b/src/menus/WindowMenus.cpp @@ -7,16 +7,14 @@ #ifdef __WXMAC__ -#include "WindowMenus.h" - #include "../CommonCommandFlags.h" #include "../Menus.h" #include "../Project.h" +#include "../ProjectFileIO.h" #include "../commands/CommandContext.h" #include #include -#include #undef USE_COCOA @@ -55,76 +53,69 @@ void DoMacMinimize(AudacityProject *project) } } -const auto menuTitle = XO("&Window"); +std::vector< wxWindowID > sReservedIds; +std::vector< std::weak_ptr< AudacityProject > > sProjects; +void RebuildMenu(wxCommandEvent &evt) +{ + // Let other listeners hear it too + evt.Skip(); + + // This is a big hammer. + // Really we just need to recreate just the Window menu. + // This causes the checkmark to be put in the right place for the + // currently active project + MenuCreator::RebuildAllMenuBars(); } +wxWindowID ReservedID( + size_t index, const std::shared_ptr< AudacityProject > &pProject ) +{ + if ( sReservedIds.empty() ) { + // Do this once only per session, and don't worry about unbinding + wxTheApp->Bind( EVT_PROJECT_ACTIVATION, RebuildMenu ); + wxTheApp->Bind( EVT_PROJECT_TITLE_CHANGE, RebuildMenu ); + } + + while ( sReservedIds.size() <= index ) + sReservedIds.emplace_back( wxIdManager::ReserveId() ); + + if ( sProjects.size() < sReservedIds.size() ) + sProjects.resize( sReservedIds.size() ); + sProjects[ index ] = pProject; + + return sReservedIds[ index ]; +} + +void OnWindow( wxCommandEvent &evt ) +{ + const auto begin = sReservedIds.begin(), end = sReservedIds.end(), + iter = std::find( begin, end, evt.GetId() ); + size_t index = iter - begin; + if ( index < sProjects.size() ) { + auto pProject = sProjects[ index ].lock(); + if ( pProject ) { + // Make it the active project + SetActiveProject(pProject.get()); + + // And ensure it's visible + wxFrame *frame = pProject->GetFrame(); + if (frame->IsIconized()) + { + frame->Restore(); + } + frame->Raise(); + } + } +} + +} // namespace + /// Namespace for functions for window management (mac only?) namespace WindowActions { // exported helper functions - -// Refreshes the Window menu in all projects -void Refresh() -{ - // Must do it in all projects - for (auto thisProject : AllProjects{}) - { - // Need the projects frame, but this should always be successful - wxFrame *frame = thisProject->GetFrame(); - wxASSERT(frame != NULL); - if (!frame) - { - continue; - } - - // This can happen if we are called before the menubar is set in the frame - wxMenuBar *bar = frame->GetMenuBar(); - if (!bar) - { - continue; - } - - // Should always find the Window menu - int pos = bar->FindMenu( menuTitle.Translation() ); - wxASSERT(pos != wxNOT_FOUND); - if (pos == wxNOT_FOUND) - { - continue; - } - - // We can not get the menu proper - wxMenu *menu = bar->GetMenu(pos); - - // Remove all existing window items - for (auto item : menu->GetMenuItems()) - { - if (item->GetId() >= WindowActions::ID_BASE) - { - menu->Destroy(item); - } - } - - // Add all projects to this project's Window menu - for (auto project : AllProjects{}) - { - int itemId = WindowActions::ID_BASE + project->GetProjectNumber(); - wxString itemName = project->GetFrame()->GetTitle(); - bool isActive = (GetActiveProject() == project.get()); - - // This should never really happen, but a menu item must have a name - if (itemName.empty()) - { - itemName = _(""); - } - - // Add it to the menu and check it if it's the active project - wxMenuItem *item = menu->Append(itemId, itemName); - item->SetCheckable(true); - item->Check(isActive); - } - } -} +// none // Menu handler functions @@ -189,12 +180,12 @@ namespace { using namespace MenuTable; BaseItemSharedPtr WindowMenu() { - ////////////////////////////////////////////////////////////////////////// - // poor imitation of the Mac Windows Menu - ////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////// + // poor imitation of the Mac Windows Menu + ////////////////////////////////////////////////////////////////////////// static BaseItemSharedPtr menu{ ( FinderScope{ findCommandHandler }, - Menu( wxT("Window"), menuTitle, + Menu( wxT("Window"), XO("&Window"), Section( "", /* i18n-hint: Standard Macintosh Window menu item: Make (the current * window) shrink to an icon on the dock */ @@ -217,10 +208,34 @@ BaseItemSharedPtr WindowMenu() Special( wxT("PopulateWindowsStep"), [](AudacityProject &, wxMenu &theMenu) { - // Should something be done here??? + // Undo previous bindings + for ( auto id : sReservedIds ) + wxTheApp->Unbind( wxEVT_MENU, OnWindow, id ); + + // Add all projects to this project's Window menu + size_t ii = 0; + for (auto project : AllProjects{}) + { + int itemId = ReservedID( ii++, project ); + wxString itemName = project->GetFrame()->GetTitle(); + bool isActive = (GetActiveProject() == project.get()); + + // This should never really happen, but a menu item must have a name + if (itemName.empty()) + { + itemName = _(""); + } + + // Add it to the menu and check it if it's the active project + wxMenuItem *item = theMenu.Append(itemId, itemName); + item->SetCheckable(true); + item->Check(isActive); + + // Bind the callback + wxTheApp->Bind( wxEVT_MENU, OnWindow, itemId ); + } } ) ) - ) ) }; return menu; } diff --git a/src/menus/WindowMenus.h b/src/menus/WindowMenus.h deleted file mode 100644 index 854752cfa..000000000 --- a/src/menus/WindowMenus.h +++ /dev/null @@ -1,22 +0,0 @@ -#include "../Audacity.h" - -#include "../commands/CommandManager.h" - -#ifdef __WXMAC__ - -/// Namespace for functions for window management (mac only?) -namespace WindowActions { - - // Range of assigned menu IDs - static const wxWindowID ID_BASE = 30000; - static const wxWindowID ID_LAST = 30999; - - // Exported helper functions - void Refresh(); -}; - -#else - -// Not WXMAC. - -#endif