diff --git a/src/AudacityApp.cpp b/src/AudacityApp.cpp index 9a8457e7a..223f82dd8 100644 --- a/src/AudacityApp.cpp +++ b/src/AudacityApp.cpp @@ -109,6 +109,10 @@ 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 @@ -885,6 +889,11 @@ 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) @@ -978,6 +987,35 @@ 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 6886d1d94..a75cf306a 100644 --- a/src/AudacityApp.h +++ b/src/AudacityApp.h @@ -69,6 +69,10 @@ 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 5b4ffb128..fa4f4f2bd 100644 --- a/src/Project.cpp +++ b/src/Project.cpp @@ -15,11 +15,14 @@ #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 { @@ -112,9 +115,13 @@ 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 4899dca82..f5122728c 100644 --- a/src/Project.h +++ b/src/Project.h @@ -93,11 +93,6 @@ 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 eaeda687f..adb7a3509 100644 --- a/src/ProjectFileIO.cpp +++ b/src/ProjectFileIO.cpp @@ -24,7 +24,9 @@ Paul Licameli split from AudacityProject.cpp #include "widgets/AudacityMessageBox.h" #include "widgets/NumericTextCtrl.h" -wxDEFINE_EVENT(EVT_PROJECT_TITLE_CHANGE, wxCommandEvent); +#if defined(__WXMAC__) +#include "menus/WindowMenus.h" +#endif static void RefreshAllTitles(bool bShowProjectNumbers ) { @@ -132,13 +134,13 @@ void ProjectFileIO::SetProjectTitle( int number) name += _("(Recovered)"); } - if ( name != window.GetTitle() ) { - window.SetTitle( name ); - window.SetName(name); // to make the nvda screen reader read the correct title + window.SetTitle( name ); + window.SetName(name); // to make the nvda screen reader read the correct title - project.QueueEvent( - safenew wxCommandEvent{ EVT_PROJECT_TITLE_CHANGE } ); - } +#if defined(__WXMAC__) + // Refresh the Window menu + WindowActions::Refresh(); +#endif } // 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 d5be00f26..c9c3c2b9a 100644 --- a/src/ProjectFileIO.h +++ b/src/ProjectFileIO.h @@ -96,9 +96,4 @@ 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 a40c0009a..c576842a4 100644 --- a/src/menus/WindowMenus.cpp +++ b/src/menus/WindowMenus.cpp @@ -7,14 +7,16 @@ #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 @@ -53,69 +55,74 @@ void DoMacMinimize(AudacityProject *project) } } -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 -// none + +// 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(wxT("Window")); + 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); + } + } +} // Menu handler functions @@ -180,9 +187,9 @@ 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"), XO("&Window"), @@ -208,34 +215,10 @@ BaseItemSharedPtr WindowMenu() Special( wxT("PopulateWindowsStep"), [](AudacityProject &, wxMenu &theMenu) { - // 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 ); - } + // Should something be done here??? } ) ) + ) ) }; return menu; } diff --git a/src/menus/WindowMenus.h b/src/menus/WindowMenus.h new file mode 100644 index 000000000..854752cfa --- /dev/null +++ b/src/menus/WindowMenus.h @@ -0,0 +1,22 @@ +#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