1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-11-21 16:37:12 +01:00

Bug1119: Windows menu on Mac should list all project names...

... Reimplemented without making dependency cycles.

Project and ProjectFileManager publish events for change of active project or
change of a project title.

WindowMenus.cpp can listen for those events, so that it can update the menu
appropriately.  So it's all done nonintrusively in the rest of the code.
This commit is contained in:
Paul Licameli
2020-04-21 13:07:01 -04:00
parent 78c2c09ba2
commit 2e3c072fd5
8 changed files with 106 additions and 154 deletions

View File

@@ -109,10 +109,6 @@ It handles initialization and termination by subclassing wxApp.
#include "tracks/ui/Scrubbing.h" #include "tracks/ui/Scrubbing.h"
#include "widgets/FileHistory.h" #include "widgets/FileHistory.h"
#if defined(__WXMAC__)
#include "menus/WindowMenus.h"
#endif
#ifdef EXPERIMENTAL_EASY_CHANGE_KEY_BINDINGS #ifdef EXPERIMENTAL_EASY_CHANGE_KEY_BINDINGS
#include "prefs/KeyConfigPrefs.h" #include "prefs/KeyConfigPrefs.h"
#endif #endif
@@ -889,11 +885,6 @@ BEGIN_EVENT_TABLE(AudacityApp, wxApp)
EVT_MENU_RANGE(FileHistory::ID_RECENT_FIRST, FileHistory::ID_RECENT_LAST, EVT_MENU_RANGE(FileHistory::ID_RECENT_FIRST, FileHistory::ID_RECENT_LAST,
AudacityApp::OnMRUFile) 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) // Handle AppCommandEvents (usually from a script)
EVT_APP_COMMAND(wxID_ANY, AudacityApp::OnReceiveCommand) EVT_APP_COMMAND(wxID_ANY, AudacityApp::OnReceiveCommand)
@@ -987,35 +978,6 @@ void AudacityApp::OnMRUFile(wxCommandEvent& event) {
history.RemoveFileFromHistory(n); 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)) void AudacityApp::OnTimer(wxTimerEvent& WXUNUSED(event))
{ {
// Filenames are queued when Audacity receives a few of the // Filenames are queued when Audacity receives a few of the

View File

@@ -69,10 +69,6 @@ class AudacityApp final : public wxApp {
// A wrapper of the above that does not throw // A wrapper of the above that does not throw
bool SafeMRUOpen(const wxString &fileName); bool SafeMRUOpen(const wxString &fileName);
#if defined(__WXMAC__)
void OnWindow(wxCommandEvent &event);
#endif
void OnReceiveCommand(AppCommandEvent &event); void OnReceiveCommand(AppCommandEvent &event);
void OnKeyDown(wxKeyEvent &event); void OnKeyDown(wxKeyEvent &event);

View File

@@ -15,14 +15,11 @@
#include "KeyboardCapture.h" #include "KeyboardCapture.h"
#include "ondemand/ODTaskThread.h" #include "ondemand/ODTaskThread.h"
#if defined(__WXMAC__)
#include "menus/WindowMenus.h"
#endif
#include <wx/display.h> #include <wx/display.h>
#include <wx/frame.h> #include <wx/frame.h>
wxDEFINE_EVENT(EVT_TRACK_PANEL_TIMER, wxCommandEvent); wxDEFINE_EVENT(EVT_TRACK_PANEL_TIMER, wxCommandEvent);
wxDEFINE_EVENT(EVT_PROJECT_ACTIVATION, wxCommandEvent);
size_t AllProjects::size() const size_t AllProjects::size() const
{ {
@@ -115,13 +112,9 @@ void SetActiveProject(AudacityProject * project)
if ( gActiveProject != project ) { if ( gActiveProject != project ) {
gActiveProject = project; gActiveProject = project;
KeyboardCapture::Capture( nullptr ); KeyboardCapture::Capture( nullptr );
wxTheApp->QueueEvent( safenew wxCommandEvent{ EVT_PROJECT_ACTIVATION } );
} }
wxTheApp->SetTopWindow( FindProjectFrame( project ) ); wxTheApp->SetTopWindow( FindProjectFrame( project ) );
#if defined(__WXMAC__)
// Refresh the Window menu
WindowActions::Refresh();
#endif
} }
AudacityProject::AudacityProject() AudacityProject::AudacityProject()

View File

@@ -93,6 +93,11 @@ using AttachedWindows = ClientData::Site<
wxDECLARE_EXPORTED_EVENT(AUDACITY_DLL_API, wxDECLARE_EXPORTED_EVENT(AUDACITY_DLL_API,
EVT_TRACK_PANEL_TIMER, wxCommandEvent); 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 ///\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 /// 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 /// sub-objects that it treats opaquely. It stores a filename and a status

View File

@@ -24,9 +24,7 @@ Paul Licameli split from AudacityProject.cpp
#include "widgets/AudacityMessageBox.h" #include "widgets/AudacityMessageBox.h"
#include "widgets/NumericTextCtrl.h" #include "widgets/NumericTextCtrl.h"
#if defined(__WXMAC__) wxDEFINE_EVENT(EVT_PROJECT_TITLE_CHANGE, wxCommandEvent);
#include "menus/WindowMenus.h"
#endif
static void RefreshAllTitles(bool bShowProjectNumbers ) static void RefreshAllTitles(bool bShowProjectNumbers )
{ {
@@ -134,13 +132,13 @@ void ProjectFileIO::SetProjectTitle( int number)
name += _("(Recovered)"); name += _("(Recovered)");
} }
if ( name != window.GetTitle() ) {
window.SetTitle( name ); window.SetTitle( name );
window.SetName(name); // to make the nvda screen reader read the correct title window.SetName(name); // to make the nvda screen reader read the correct title
#if defined(__WXMAC__) project.QueueEvent(
// Refresh the Window menu safenew wxCommandEvent{ EVT_PROJECT_TITLE_CHANGE } );
WindowActions::Refresh(); }
#endif
} }
// Most of this string was duplicated 3 places. Made the warning consistent in this global. // Most of this string was duplicated 3 places. Made the warning consistent in this global.

View File

@@ -96,4 +96,9 @@ public:
size_t UnnamedCount; 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 #endif

View File

@@ -7,16 +7,14 @@
#ifdef __WXMAC__ #ifdef __WXMAC__
#include "WindowMenus.h"
#include "../CommonCommandFlags.h" #include "../CommonCommandFlags.h"
#include "../Menus.h" #include "../Menus.h"
#include "../Project.h" #include "../Project.h"
#include "../ProjectFileIO.h"
#include "../commands/CommandContext.h" #include "../commands/CommandContext.h"
#include <wx/frame.h> #include <wx/frame.h>
#include <wx/menu.h> #include <wx/menu.h>
#include <wx/menuitem.h>
#undef USE_COCOA #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 for functions for window management (mac only?)
namespace WindowActions { namespace WindowActions {
// exported helper functions // 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( 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 = _("<untitled>");
}
// 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 // Menu handler functions
@@ -194,7 +185,7 @@ BaseItemSharedPtr WindowMenu()
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
static BaseItemSharedPtr menu{ static BaseItemSharedPtr menu{
( FinderScope{ findCommandHandler }, ( FinderScope{ findCommandHandler },
Menu( wxT("Window"), menuTitle, Menu( wxT("Window"), XO("&Window"),
Section( "", Section( "",
/* i18n-hint: Standard Macintosh Window menu item: Make (the current /* i18n-hint: Standard Macintosh Window menu item: Make (the current
* window) shrink to an icon on the dock */ * window) shrink to an icon on the dock */
@@ -217,10 +208,34 @@ BaseItemSharedPtr WindowMenu()
Special( wxT("PopulateWindowsStep"), Special( wxT("PopulateWindowsStep"),
[](AudacityProject &, wxMenu &theMenu) [](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 = _("<untitled>");
}
// 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; return menu;
} }

View File

@@ -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