diff --git a/src/tracks/playabletrack/wavetrack/ui/SpectrumVZoomHandle.cpp b/src/tracks/playabletrack/wavetrack/ui/SpectrumVZoomHandle.cpp index c91420d47..fa90f05d4 100644 --- a/src/tracks/playabletrack/wavetrack/ui/SpectrumVZoomHandle.cpp +++ b/src/tracks/playabletrack/wavetrack/ui/SpectrumVZoomHandle.cpp @@ -265,16 +265,6 @@ PopupMenuTable &SpectrumVRulerMenuTable::Instance() return instance; } -void SpectrumVRulerMenuTable::InitMenu(wxMenu *pMenu) -{ - WaveTrackVRulerMenuTable::InitMenu(pMenu); - - WaveTrack *const wt = mpData->pTrack; - const int id = - OnFirstSpectrumScaleID + (int)(wt->GetSpectrogramSettings().scaleType); - pMenu->Check(id, true); -} - BEGIN_POPUP_MENU(SpectrumVRulerMenuTable) BeginSection( "Scales" ); @@ -283,7 +273,17 @@ BeginSection( "Scales" ); for (int ii = 0, nn = names.size(); ii < nn; ++ii) { AppendRadioItem( names[ii].Internal(), OnFirstSpectrumScaleID + ii, names[ii].Msgid(), - POPUP_MENU_FN( OnSpectrumScaleType ) ); + POPUP_MENU_FN( OnSpectrumScaleType ), + []( PopupMenuHandler &handler, wxMenu &menu, int id ){ + WaveTrack *const wt = + static_cast( handler ) + .mpData->pTrack; + if ( id == + OnFirstSpectrumScaleID + + (int)(wt->GetSpectrogramSettings().scaleType ) ) + menu.Check(id, true); + } + ); } } EndSection(); diff --git a/src/tracks/playabletrack/wavetrack/ui/SpectrumVZoomHandle.h b/src/tracks/playabletrack/wavetrack/ui/SpectrumVZoomHandle.h index 8fa691b54..1a7348252 100644 --- a/src/tracks/playabletrack/wavetrack/ui/SpectrumVZoomHandle.h +++ b/src/tracks/playabletrack/wavetrack/ui/SpectrumVZoomHandle.h @@ -85,8 +85,6 @@ public: static PopupMenuTable &Instance(); private: - void InitMenu(wxMenu *pMenu) override; - void OnSpectrumScaleType(wxCommandEvent &evt); }; diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveTrackControls.cpp b/src/tracks/playabletrack/wavetrack/ui/WaveTrackControls.cpp index f3223479d..75148237e 100644 --- a/src/tracks/playabletrack/wavetrack/ui/WaveTrackControls.cpp +++ b/src/tracks/playabletrack/wavetrack/ui/WaveTrackControls.cpp @@ -43,22 +43,6 @@ Paul Licameli split from TrackPanel.cpp #include #include -namespace -{ - /// Puts a check mark at a given position in a menu. - template - void SetMenuChecks(wxMenu & menu, const Pred &pred) - { - for (auto &item : menu.GetMenuItems()) - { - if (item->IsCheckable()) { - auto id = item->GetId(); - menu.Check(id, pred(id)); - } - } - } -} - WaveTrackControls::~WaveTrackControls() { } @@ -148,21 +132,38 @@ enum { }; +namespace { +using ValueFinder = std::function< int( WaveTrack& ) >; + +// A function that makes functions that check and enable sub-menu items, +// parametrized by how you get the relevant value from a track's settings +template< typename Table > +PopupMenuTableEntry::InitFunction initFn( const ValueFinder &findValue ) +{ + return [findValue]( PopupMenuHandler &handler, wxMenu &menu, int id ){ + auto pData = static_cast( handler ).mpData; + const auto pTrack = static_cast(pData->pTrack); + auto &project = pData->project; + bool unsafe = ProjectAudioIO::Get( project ).IsAudioActive(); + + menu.Check( id, id == findValue( *pTrack ) ); + menu.Enable( id, !unsafe ); + }; +}; +} + //============================================================================= // Table class for a sub-menu -class WaveColorMenuTable : public PopupMenuTable +struct WaveColorMenuTable : PopupMenuTable { WaveColorMenuTable() : PopupMenuTable( "WaveColor", XO("&Wave Color") ) {} DECLARE_POPUP_MENU(WaveColorMenuTable); -public: static WaveColorMenuTable &Instance(); -private: void InitUserData(void *pUserData) override; - void InitMenu(wxMenu *pMenu) override; void DestroyMenu() override { @@ -171,7 +172,7 @@ private: PlayableTrackControls::InitMenuData *mpData{}; - int IdOfWaveColor(int WaveColor); + static int IdOfWaveColor(int WaveColor); void OnWaveColorChange(wxCommandEvent & event); }; @@ -186,34 +187,27 @@ void WaveColorMenuTable::InitUserData(void *pUserData) mpData = static_cast(pUserData); } -void WaveColorMenuTable::InitMenu(wxMenu *pMenu) -{ - WaveTrack *const pTrack = static_cast(mpData->pTrack); - auto WaveColorId = IdOfWaveColor( pTrack->GetWaveColorIndex()); - SetMenuChecks(*pMenu, [=](int id){ return id == WaveColorId; }); - - AudacityProject *const project = &mpData->project; - bool unsafe = ProjectAudioIO::Get( *project ).IsAudioActive(); - for (int i = OnInstrument1ID; i <= OnInstrument4ID; i++) { - pMenu->Enable(i, !unsafe); - } -} - const TranslatableString GetWaveColorStr(int colorIndex) { return XO("Instrument %i").Format( colorIndex+1 ); } - BEGIN_POPUP_MENU(WaveColorMenuTable) + static const auto fn = initFn< WaveColorMenuTable >( + []( WaveTrack &track ){ + return IdOfWaveColor( track.GetWaveColorIndex() ); + } + ); + AppendRadioItem( "Instrument1", OnInstrument1ID, - GetWaveColorStr(0), POPUP_MENU_FN( OnWaveColorChange ) ); + GetWaveColorStr(0), POPUP_MENU_FN( OnWaveColorChange ), fn ); AppendRadioItem( "Instrument2", OnInstrument2ID, - GetWaveColorStr(1), POPUP_MENU_FN( OnWaveColorChange ) ); + GetWaveColorStr(1), POPUP_MENU_FN( OnWaveColorChange ), fn ); AppendRadioItem( "Instrument3", OnInstrument3ID, - GetWaveColorStr(2), POPUP_MENU_FN( OnWaveColorChange ) ); + GetWaveColorStr(2), POPUP_MENU_FN( OnWaveColorChange ), fn ); AppendRadioItem( "Instrument4", OnInstrument4ID, - GetWaveColorStr(3), POPUP_MENU_FN( OnWaveColorChange ) ); + GetWaveColorStr(3), POPUP_MENU_FN( OnWaveColorChange ), fn ); + END_POPUP_MENU() /// Converts a WaveColor enumeration to a wxWidgets menu item Id. @@ -249,19 +243,16 @@ void WaveColorMenuTable::OnWaveColorChange(wxCommandEvent & event) //============================================================================= // Table class for a sub-menu -class FormatMenuTable : public PopupMenuTable +struct FormatMenuTable : PopupMenuTable { FormatMenuTable() : PopupMenuTable{ "SampleFormat", XO("&Format") } {} DECLARE_POPUP_MENU(FormatMenuTable); -public: static FormatMenuTable &Instance(); -private: void InitUserData(void *pUserData) override; - void InitMenu(wxMenu *pMenu) override; void DestroyMenu() override { @@ -270,7 +261,7 @@ private: PlayableTrackControls::InitMenuData *mpData{}; - int IdOfFormat(int format); + static int IdOfFormat(int format); void OnFormatChange(wxCommandEvent & event); }; @@ -286,26 +277,21 @@ void FormatMenuTable::InitUserData(void *pUserData) mpData = static_cast(pUserData); } -void FormatMenuTable::InitMenu(wxMenu *pMenu) -{ - WaveTrack *const pTrack = static_cast(mpData->pTrack); - auto formatId = IdOfFormat(pTrack->GetSampleFormat()); - SetMenuChecks(*pMenu, [=](int id){ return id == formatId; }); - - AudacityProject *const project = &mpData->project; - bool unsafe = ProjectAudioIO::Get( *project ).IsAudioActive(); - for (int i = On16BitID; i <= OnFloatID; i++) { - pMenu->Enable(i, !unsafe); - } -} BEGIN_POPUP_MENU(FormatMenuTable) + static const auto fn = initFn< FormatMenuTable >( + []( WaveTrack &track ){ + return IdOfFormat( track.GetSampleFormat() ); + } + ); + AppendRadioItem( "16Bit", On16BitID, - GetSampleFormatStr(int16Sample), POPUP_MENU_FN( OnFormatChange ) ); + GetSampleFormatStr(int16Sample), POPUP_MENU_FN( OnFormatChange ), fn ); AppendRadioItem("24Bit", On24BitID, - GetSampleFormatStr( int24Sample), POPUP_MENU_FN( OnFormatChange ) ); + GetSampleFormatStr( int24Sample), POPUP_MENU_FN( OnFormatChange ), fn ); AppendRadioItem( "Float", OnFloatID, - GetSampleFormatStr(floatSample), POPUP_MENU_FN( OnFormatChange ) ); + GetSampleFormatStr(floatSample), POPUP_MENU_FN( OnFormatChange ), fn ); + END_POPUP_MENU() /// Converts a format enumeration to a wxWidgets menu item Id. @@ -372,19 +358,16 @@ void FormatMenuTable::OnFormatChange(wxCommandEvent & event) //============================================================================= // Table class for a sub-menu -class RateMenuTable : public PopupMenuTable +struct RateMenuTable : PopupMenuTable { RateMenuTable() : PopupMenuTable{ "SampleRate", XO("Rat&e") } {} DECLARE_POPUP_MENU(RateMenuTable); -public: static RateMenuTable &Instance(); -private: void InitUserData(void *pUserData) override; - void InitMenu(wxMenu *pMenu) override; void DestroyMenu() override { @@ -393,7 +376,7 @@ private: PlayableTrackControls::InitMenuData *mpData{}; - int IdOfRate(int rate); + static int IdOfRate(int rate); void SetRate(WaveTrack * pTrack, double rate); void OnRateChange(wxCommandEvent & event); @@ -411,36 +394,30 @@ void RateMenuTable::InitUserData(void *pUserData) mpData = static_cast(pUserData); } -void RateMenuTable::InitMenu(wxMenu *pMenu) -{ - WaveTrack *const pTrack = static_cast(mpData->pTrack); - const auto rateId = IdOfRate((int)pTrack->GetRate()); - SetMenuChecks(*pMenu, [=](int id){ return id == rateId; }); - - AudacityProject *const project = &mpData->project; - bool unsafe = ProjectAudioIO::Get( *project ).IsAudioActive(); - for (int i = OnRate8ID; i <= OnRateOtherID; i++) { - pMenu->Enable(i, !unsafe); - } -} - // Because of Bug 1780 we can't use AppendRadioItem // If we did, we'd get no message when clicking on Other... // when it is already selected. BEGIN_POPUP_MENU(RateMenuTable) - AppendCheckItem( "8000", OnRate8ID, XO("8000 Hz"), POPUP_MENU_FN( OnRateChange ) ); - AppendCheckItem( "11025", OnRate11ID, XO("11025 Hz"), POPUP_MENU_FN( OnRateChange ) ); - AppendCheckItem( "16000", OnRate16ID, XO("16000 Hz"), POPUP_MENU_FN( OnRateChange ) ); - AppendCheckItem( "22050", OnRate22ID, XO("22050 Hz"), POPUP_MENU_FN( OnRateChange ) ); - AppendCheckItem( "44100", OnRate44ID, XO("44100 Hz"), POPUP_MENU_FN( OnRateChange ) ); - AppendCheckItem( "48000", OnRate48ID, XO("48000 Hz"), POPUP_MENU_FN( OnRateChange ) ); - AppendCheckItem( "88200", OnRate88ID, XO("88200 Hz"), POPUP_MENU_FN( OnRateChange ) ); - AppendCheckItem( "96000", OnRate96ID, XO("96000 Hz"), POPUP_MENU_FN( OnRateChange ) ); - AppendCheckItem( "176400", OnRate176ID, XO("176400 Hz"), POPUP_MENU_FN( OnRateChange ) ); - AppendCheckItem( "192000", OnRate192ID, XO("192000 Hz"), POPUP_MENU_FN( OnRateChange ) ); - AppendCheckItem( "352800", OnRate352ID, XO("352800 Hz"), POPUP_MENU_FN( OnRateChange ) ); - AppendCheckItem( "384000", OnRate384ID, XO("384000 Hz"), POPUP_MENU_FN( OnRateChange ) ); - AppendCheckItem( "Other", OnRateOtherID, XO("&Other..."), POPUP_MENU_FN( OnRateOther ) ); + static const auto fn = initFn< RateMenuTable >( + []( WaveTrack &track ){ + return IdOfRate( (int)track.GetRate() ); + } + ); + + AppendCheckItem( "8000", OnRate8ID, XO("8000 Hz"), POPUP_MENU_FN( OnRateChange ), fn ); + AppendCheckItem( "11025", OnRate11ID, XO("11025 Hz"), POPUP_MENU_FN( OnRateChange ), fn ); + AppendCheckItem( "16000", OnRate16ID, XO("16000 Hz"), POPUP_MENU_FN( OnRateChange ), fn ); + AppendCheckItem( "22050", OnRate22ID, XO("22050 Hz"), POPUP_MENU_FN( OnRateChange ), fn ); + AppendCheckItem( "44100", OnRate44ID, XO("44100 Hz"), POPUP_MENU_FN( OnRateChange ), fn ); + AppendCheckItem( "48000", OnRate48ID, XO("48000 Hz"), POPUP_MENU_FN( OnRateChange ), fn ); + AppendCheckItem( "88200", OnRate88ID, XO("88200 Hz"), POPUP_MENU_FN( OnRateChange ), fn ); + AppendCheckItem( "96000", OnRate96ID, XO("96000 Hz"), POPUP_MENU_FN( OnRateChange ), fn ); + AppendCheckItem( "176400", OnRate176ID, XO("176400 Hz"), POPUP_MENU_FN( OnRateChange ), fn ); + AppendCheckItem( "192000", OnRate192ID, XO("192000 Hz"), POPUP_MENU_FN( OnRateChange ), fn ); + AppendCheckItem( "352800", OnRate352ID, XO("352800 Hz"), POPUP_MENU_FN( OnRateChange ), fn ); + AppendCheckItem( "384000", OnRate384ID, XO("384000 Hz"), POPUP_MENU_FN( OnRateChange ), fn ); + AppendCheckItem( "Other", OnRateOtherID, XO("&Other..."), POPUP_MENU_FN( OnRateOther ), fn ); + END_POPUP_MENU() const int nRates = 12; @@ -573,19 +550,16 @@ void RateMenuTable::OnRateOther(wxCommandEvent &) //============================================================================= // Class defining common command handlers for mono and stereo tracks -class WaveTrackMenuTable : public PopupMenuTable +struct WaveTrackMenuTable : PopupMenuTable { -public: static WaveTrackMenuTable &Instance( Track * pTrack); Track * mpTrack{}; -protected: WaveTrackMenuTable() : PopupMenuTable{ "WaveTrack" } {} void InitUserData(void *pUserData) override; - void InitMenu(wxMenu *pMenu) override; void DestroyMenu() override { @@ -639,146 +613,81 @@ static std::vector AllTypes() return result; } -void WaveTrackMenuTable::InitMenu(wxMenu *pMenu) -{ - WaveTrack *const pTrack = static_cast(mpData->pTrack); - - std::vector checkedIds; - - const auto &view = WaveTrackView::Get( *pTrack ); - auto multiView = view.GetMultiView(); - if (multiView) - checkedIds.push_back( OnMultiViewID ); - - bool hasSpectrum = false; - int uniqueDisplay = 0; - { - // Find the set of type ids of the shown displays, disregarding their - // top-to-bottom arrangement - auto displays = view.GetDisplays(); - const auto end = displays.end(); - auto iter = displays.begin(); - std::sort( iter, end ); - - // Check the corresponding menu items, and decide which if any has - // the unique check - int displayId = OnSetDisplayId; - int nDisplays = 0; - for ( const auto &type : AllTypes() ) { - if ( iter != end && iter->id == type.id ) { - checkedIds.push_back( displayId ); - uniqueDisplay = displayId; - ++iter; - ++nDisplays; - - // Unfortunately this special knowledge of the Spectrum view type - // remains. In future, either let a registry system insert this - // menu item, or (better) move it to the context menu of the - // Spectrum vertical ruler. - // (But the latter won't be satisfactory without a means to - // open that other context menu with keystrokes only, and that - // would require some notion of a focused sub-view.) - hasSpectrum = ( type.id == WaveTrackViewConstants::Spectrum ); - } - ++displayId; - } - if ( nDisplays > 1 ) - uniqueDisplay = 0; - } - - if ( multiView && uniqueDisplay ) - // Bug2275 residual - // Disable the checking-off of the only sub-view - pMenu->Enable( uniqueDisplay, false ); - - // Bug 1253. Shouldn't open preferences if audio is busy. - // We can't change them on the fly yet anyway. - auto gAudioIO = AudioIOBase::Get(); - if ( hasSpectrum ) - pMenu->Enable(OnSpectrogramSettingsID, !gAudioIO->IsBusy()); - - AudacityProject *const project = &mpData->project; - auto &tracks = TrackList::Get( *project ); - bool unsafe = RealtimeEffectManager::Get().RealtimeIsActive() && - ProjectAudioIO::Get( *project ).IsAudioActive(); - - auto nChannels = TrackList::Channels(pTrack).size(); - const bool isMono = ( nChannels == 1 ); - const bool isStereo = ( nChannels == 2 ); - // Maybe more than stereo tracks some time? - - if ( isMono ) - { - WaveTrack *const pTrack2 = static_cast(mpData->pTrack); - - auto next = * ++ tracks.Find(pTrack2); - - if (isMono) { - const bool canMakeStereo = - (next && - TrackList::Channels(next).size() == 1 && - track_cast(next)); - - pMenu->Enable(OnMergeStereoID, canMakeStereo && !unsafe); - - int itemId; - switch (pTrack2->GetChannel()) { - case Track::LeftChannel: - itemId = OnChannelLeftID; - break; - case Track::RightChannel: - itemId = OnChannelRightID; - break; - default: - itemId = OnChannelMonoID; - break; - } - checkedIds.push_back(itemId); - } - } - else - { - pMenu->Enable(OnMergeStereoID, false); - } - - SetMenuChecks(*pMenu, [&](int id){ - auto end = checkedIds.end(); - return end != std::find(checkedIds.begin(), end, id); - }); - - // Enable this only for properly stereo tracks: - pMenu->Enable(OnSwapChannelsID, isStereo && !unsafe); - pMenu->Enable(OnSplitStereoID, !isMono && !unsafe); - -#ifndef EXPERIMENTAL_DA - // Can be achieved by split stereo and then dragging pan slider. - pMenu->Enable(OnSplitStereoMonoID, !isMono && !unsafe); -#endif - - // Several menu items no longer needed.... -#if 0 - pMenu->Enable(OnChannelMonoID, isMono); - pMenu->Enable(OnChannelLeftID, isMono); - pMenu->Enable(OnChannelRightID, isMono); -#endif -} - BEGIN_POPUP_MENU(WaveTrackMenuTable) + // Functions usable "now" (list population time) and also "later" + // (in callbacks to check and disable items) + static const auto findTrack = + []( PopupMenuHandler &handler ) -> WaveTrack & { + return *static_cast< WaveTrack* >( + static_cast< WaveTrackMenuTable& >( handler ).mpData->pTrack); + }; - WaveTrack *const pTrack = static_cast(mpTrack); + static const auto isMono = + []( PopupMenuHandler &handler ) -> bool { + return 1 == TrackList::Channels( &findTrack( handler ) ).size(); + }; + + static const auto isUnsafe = + []( PopupMenuHandler &handler ) -> bool { + auto &project = + static_cast< WaveTrackMenuTable& >( handler ).mpData->project; + return RealtimeEffectManager::Get().RealtimeIsActive() && + ProjectAudioIO::Get( project ).IsAudioActive(); + }; + + const auto pTrack = &findTrack( *this ); const auto &view = WaveTrackView::Get( *pTrack ); BeginSection( "SubViews" ); if ( WaveTrackSubViews::slots() > 1 ) - AppendCheckItem( "MultiView", OnMultiViewID, XO("&Multi-view"), POPUP_MENU_FN( OnMultiView ) ); + AppendCheckItem( "MultiView", OnMultiViewID, XO("&Multi-view"), + POPUP_MENU_FN( OnMultiView ), + []( PopupMenuHandler &handler, wxMenu &menu, int id ){ + auto &track = findTrack( handler ); + const auto &view = WaveTrackView::Get( track ); + menu.Check( id, view.GetMultiView() ); + } + ); int id = OnSetDisplayId; for ( const auto &type : AllTypes() ) { + static const auto initFn = []( bool radio ){ return + [radio]( PopupMenuHandler &handler, wxMenu &menu, int id ){ + // Find all known sub-view types + const auto allTypes = AllTypes(); + + // How to convert a type to a menu item id + const auto IdForType = + [&allTypes]( const WaveTrackSubViewType &type ) -> int { + const auto begin = allTypes.begin(); + return OnSetDisplayId + + (std::find( begin, allTypes.end(), type ) - begin); + }; + + auto &track = findTrack( handler ); + const auto &view = WaveTrackView::Get( track ); + + const auto displays = view.GetDisplays(); + const auto end = displays.end(); + bool check = (end != + std::find_if( displays.begin(), end, + [&]( const WaveTrackSubViewType &type ){ + return id == IdForType( type ); } ) ); + menu.Check( id, check ); + + // Bug2275 residual + // Disable the checking-off of the only sub-view + if ( !radio && displays.size() == 1 && check ) + menu.Enable( id, false ); + }; + }; if ( view.GetMultiView() ) { - AppendCheckItem( type.name.Internal(), id++, type.name.Msgid(), POPUP_MENU_FN( OnSetDisplay ) ); + AppendCheckItem( type.name.Internal(), id++, type.name.Msgid(), + POPUP_MENU_FN( OnSetDisplay ), initFn( false ) ); } else { - AppendRadioItem( type.name.Internal(), id++, type.name.Msgid(), POPUP_MENU_FN( OnSetDisplay ) ); + AppendRadioItem( type.name.Internal(), id++, type.name.Msgid(), + POPUP_MENU_FN( OnSetDisplay ), initFn( true ) ); } } EndSection(); @@ -801,8 +710,21 @@ BEGIN_POPUP_MENU(WaveTrackMenuTable) WaveTrackSubView::Type{ WaveTrackViewConstants::Spectrum, {} } ) ); if( hasSpectrum ){ + // In future, we might move this to the context menu of the + // Spectrum vertical ruler. + // (But the latter won't be satisfactory without a means to + // open that other context menu with keystrokes only, and that + // would require some notion of a focused sub-view.) + BeginSection( "SpectrogramSettings" ); - AppendItem( "SpectrogramSettings", OnSpectrogramSettingsID, XO("S&pectrogram Settings..."), POPUP_MENU_FN( OnSpectrogramSettings ) ); + AppendItem( "SpectrogramSettings", OnSpectrogramSettingsID, XO("S&pectrogram Settings..."), POPUP_MENU_FN( OnSpectrogramSettings ), + []( PopupMenuHandler &handler, wxMenu &menu, int id ){ + // Bug 1253. Shouldn't open preferences if audio is busy. + // We can't change them on the fly yet anyway. + auto gAudioIO = AudioIOBase::Get(); + menu.Enable(id, !gAudioIO->IsBusy()); + } + ); EndSection(); } @@ -811,16 +733,65 @@ BEGIN_POPUP_MENU(WaveTrackMenuTable) BeginSection( "Channels" ); // If these are enabled again, choose a hot key for Mono that does not conflict // with Multi View - // AppendRadioItem(OnChannelMonoID, XO("&Mono"), OnChannelChange) - // AppendRadioItem(OnChannelLeftID, XO("&Left Channel"), OnChannelChange) - // AppendRadioItem(OnChannelRightID, XO("R&ight Channel"), OnChannelChange) - AppendItem( "MakeStereo", OnMergeStereoID, XO("Ma&ke Stereo Track"), POPUP_MENU_FN( OnMergeStereo ) ); + // AppendRadioItem(OnChannelMonoID, XO("&Mono"), + // POPUP_MENU_FN( OnChannelChange ), + // []( PopupMenuHandler &handler, wxMenu &menu, int id ){ + // menu.Enable( id, isMono( handler ) ); + // menu.Check( id, findTrack( handler ).GetChannel() == Track::MonoChannel ); + // } + // ); + // AppendRadioItem(OnChannelLeftID, XO("&Left Channel"), + // POPUP_MENU_FN( OnChannelChange ), + // []( PopupMenuHandler &handler, wxMenu &menu, int id ){ + // menu.Enable( id, isMono( handler ) ); + // menu.Check( id, findTrack( handler ).GetChannel() == Track::LeftChannel ); + // } + // ); + // AppendRadioItem(OnChannelRightID, XO("R&ight Channel"), + // POPUP_MENU_FN( OnChannelChange ), + // []( PopupMenuHandler &handler, wxMenu &menu, int id ){ + // menu.Enable( id, isMono( handler ) ); + // menu.Check( id, findTrack( handler ).GetChannel() == Track::RightChannel ); + // } + // ); + AppendItem( "MakeStereo", OnMergeStereoID, XO("Ma&ke Stereo Track"), + POPUP_MENU_FN( OnMergeStereo ), + []( PopupMenuHandler &handler, wxMenu &menu, int id ){ + bool canMakeStereo = !isUnsafe( handler ) && isMono( handler ); + if ( canMakeStereo ) { + AudacityProject &project = + static_cast< WaveTrackMenuTable& >( handler ).mpData->project; + auto &tracks = TrackList::Get( project ); + auto &track = findTrack( handler ); + auto next = * ++ tracks.Find(&track); + canMakeStereo = + (next && + TrackList::Channels(next).size() == 1 && + track_cast(next)); + } + menu.Enable( id, canMakeStereo ); + } + ); - AppendItem( "Swap", OnSwapChannelsID, XO("Swap Stereo &Channels"), POPUP_MENU_FN( OnSwapChannels ) ); - AppendItem( "Split", OnSplitStereoID, XO("Spl&it Stereo Track"), POPUP_MENU_FN( OnSplitStereo ) ); + AppendItem( "Swap", OnSwapChannelsID, XO("Swap Stereo &Channels"), + POPUP_MENU_FN( OnSwapChannels ), + []( PopupMenuHandler &handler, wxMenu &menu, int id ){ + bool isStereo = + 2 == TrackList::Channels( &findTrack( handler ) ).size(); + menu.Enable( id, isStereo && !isUnsafe( handler ) ); + } + ); + + static const auto enableSplitStereo = + []( PopupMenuHandler &handler, wxMenu &menu, int id ){ + menu.Enable( id, !isMono( handler ) && !isUnsafe( handler ) ); + }; + + AppendItem( "Split", OnSplitStereoID, XO("Spl&it Stereo Track"), + POPUP_MENU_FN( OnSplitStereo ), enableSplitStereo ); // DA: Uses split stereo track and then drag pan sliders for split-stereo-to-mono #ifndef EXPERIMENTAL_DA - AppendItem( "SplitToMono", OnSplitStereoMonoID, XO("Split Stereo to Mo&no"), POPUP_MENU_FN( OnSplitStereoMono ) ); + AppendItem( "SplitToMono", OnSplitStereoMonoID, XO("Split Stereo to Mo&no"), POPUP_MENU_FN( OnSplitStereoMono ), enableSplitStereo ); #endif EndSection(); diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveformVZoomHandle.cpp b/src/tracks/playabletrack/wavetrack/ui/WaveformVZoomHandle.cpp index b3dfb7777..56f66fc03 100644 --- a/src/tracks/playabletrack/wavetrack/ui/WaveformVZoomHandle.cpp +++ b/src/tracks/playabletrack/wavetrack/ui/WaveformVZoomHandle.cpp @@ -266,17 +266,6 @@ PopupMenuTable &WaveformVRulerMenuTable::Instance() return instance; } -void WaveformVRulerMenuTable::InitMenu(wxMenu *pMenu) -{ - WaveTrackVRulerMenuTable::InitMenu(pMenu); - -// DB setting is already on track drop down. - WaveTrack *const wt = mpData->pTrack; - const int id = - OnFirstWaveformScaleID + (int)(wt->GetWaveformSettings().scaleType); - pMenu->Check(id, true); -} - BEGIN_POPUP_MENU(WaveformVRulerMenuTable) BeginSection( "Scales" ); @@ -285,7 +274,17 @@ BEGIN_POPUP_MENU(WaveformVRulerMenuTable) for (int ii = 0, nn = names.size(); ii < nn; ++ii) { AppendRadioItem( names[ii].Internal(), OnFirstWaveformScaleID + ii, names[ii].Msgid(), - POPUP_MENU_FN( OnWaveformScaleType ) ); + POPUP_MENU_FN( OnWaveformScaleType ), + []( PopupMenuHandler &handler, wxMenu &menu, int id ){ + const auto pData = + static_cast< WaveformVRulerMenuTable& >( handler ).mpData; + WaveTrack *const wt = pData->pTrack; + if ( id == + OnFirstWaveformScaleID + + (int)(wt->GetWaveformSettings().scaleType) ) + menu.Check(id, true); + } + ); } } EndSection(); diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveformVZoomHandle.h b/src/tracks/playabletrack/wavetrack/ui/WaveformVZoomHandle.h index 15fb7ada2..c3f3529c5 100644 --- a/src/tracks/playabletrack/wavetrack/ui/WaveformVZoomHandle.h +++ b/src/tracks/playabletrack/wavetrack/ui/WaveformVZoomHandle.h @@ -85,8 +85,6 @@ public: static PopupMenuTable &Instance(); private: - virtual void InitMenu(wxMenu *pMenu) override; - void OnWaveformScaleType(wxCommandEvent &evt); }; diff --git a/src/tracks/timetrack/ui/TimeTrackControls.cpp b/src/tracks/timetrack/ui/TimeTrackControls.cpp index 0b3fc0ba9..ee99a6595 100644 --- a/src/tracks/timetrack/ui/TimeTrackControls.cpp +++ b/src/tracks/timetrack/ui/TimeTrackControls.cpp @@ -54,17 +54,6 @@ private: mpData = static_cast(pUserData); } - void InitMenu(wxMenu *pMenu) override - { - TimeTrack *const pTrack = static_cast(mpData->pTrack); - - pMenu->Check(OnTimeTrackLogIntID, pTrack->GetInterpolateLog()); - - auto isLog = pTrack->GetDisplayLog(); - pMenu->Check(OnTimeTrackLinID, !isLog); - pMenu->Check(OnTimeTrackLogID, isLog); - } - void DestroyMenu() override { mpData = nullptr; @@ -162,15 +151,34 @@ void TimeTrackMenuTable::OnTimeTrackLogInt(wxCommandEvent & /*event*/) } BEGIN_POPUP_MENU(TimeTrackMenuTable) + static const auto findTrack = []( PopupMenuHandler &handler ){ + return static_cast( + static_cast( handler ).mpData->pTrack ); + }; + BeginSection( "Scales" ); - AppendRadioItem( "Linear", OnTimeTrackLinID, XO("&Linear scale"), POPUP_MENU_FN( OnTimeTrackLin ) ); - AppendRadioItem( "Log", OnTimeTrackLogID, XO("L&ogarithmic scale"), POPUP_MENU_FN( OnTimeTrackLog ) ); + AppendRadioItem( "Linear", OnTimeTrackLinID, XO("&Linear scale"), + POPUP_MENU_FN( OnTimeTrackLin ), + []( PopupMenuHandler &handler, wxMenu &menu, int id ){ + menu.Check( id, !findTrack(handler)->GetDisplayLog() ); + } ); + AppendRadioItem( "Log", OnTimeTrackLogID, XO("L&ogarithmic scale"), + POPUP_MENU_FN( OnTimeTrackLog ), + []( PopupMenuHandler &handler, wxMenu &menu, int id ){ + menu.Check( id, findTrack(handler)->GetDisplayLog() ); + } ); EndSection(); BeginSection( "Other" ); - AppendItem( "Range", OnSetTimeTrackRangeID, XO("&Range..."), POPUP_MENU_FN( OnSetTimeTrackRange ) ); - AppendCheckItem( "LogInterp", OnTimeTrackLogIntID, XO("Logarithmic &Interpolation"), POPUP_MENU_FN( OnTimeTrackLogInt) ); + AppendItem( "Range", OnSetTimeTrackRangeID, XO("&Range..."), + POPUP_MENU_FN( OnSetTimeTrackRange ) ); + AppendCheckItem( "LogInterp", OnTimeTrackLogIntID, + XO("Logarithmic &Interpolation"), POPUP_MENU_FN( OnTimeTrackLogInt), + []( PopupMenuHandler &handler, wxMenu &menu, int id ){ + menu.Check( id, findTrack(handler)->GetInterpolateLog() ); + } ); EndSection(); + END_POPUP_MENU() PopupMenuTable *TimeTrackControls::GetMenuExtension(Track *) diff --git a/src/tracks/ui/CommonTrackControls.cpp b/src/tracks/ui/CommonTrackControls.cpp index 2720eabf9..e4342bd6e 100644 --- a/src/tracks/ui/CommonTrackControls.cpp +++ b/src/tracks/ui/CommonTrackControls.cpp @@ -93,7 +93,6 @@ private: void OnMoveTrack(wxCommandEvent &event); void InitUserData(void *pUserData) override; - void InitMenu(wxMenu *pMenu) override; void DestroyMenu() override { @@ -114,19 +113,17 @@ void TrackMenuTable::InitUserData(void *pUserData) mpData = static_cast(pUserData); } -void TrackMenuTable::InitMenu(wxMenu *pMenu) -{ - Track *const pTrack = mpData->pTrack; - - const auto &tracks = TrackList::Get( mpData->project ); - - pMenu->Enable(OnMoveUpID, tracks.CanMoveUp(pTrack)); - pMenu->Enable(OnMoveDownID, tracks.CanMoveDown(pTrack)); - pMenu->Enable(OnMoveTopID, tracks.CanMoveUp(pTrack)); - pMenu->Enable(OnMoveBottomID, tracks.CanMoveDown(pTrack)); -} - BEGIN_POPUP_MENU(TrackMenuTable) + static const auto enableIfCanMove = [](bool up){ return + [up]( PopupMenuHandler &handler, wxMenu &menu, int id ){ + auto pData = static_cast( handler ).mpData; + const auto &tracks = TrackList::Get( pData->project ); + Track *const pTrack = pData->pTrack; + menu.Enable( id, + up ? tracks.CanMoveUp(pTrack) : tracks.CanMoveDown(pTrack) ); + }; + }; + BeginSection( "Basic" ); AppendItem( "Name", OnSetNameID, XO("&Name..."), POPUP_MENU_FN( OnSetName ) ); EndSection(); @@ -143,7 +140,7 @@ BEGIN_POPUP_MENU(TrackMenuTable) GetKeyFromName(wxT("TrackMoveUp")).GET() ), wxT("\t") ), - POPUP_MENU_FN( OnMoveTrack ) ); + POPUP_MENU_FN( OnMoveTrack ), enableIfCanMove(true) ); AppendItem( "Down", OnMoveDownID, XO("Move Track &Down").Join( @@ -153,7 +150,7 @@ BEGIN_POPUP_MENU(TrackMenuTable) GetKeyFromName(wxT("TrackMoveDown")).GET() ), wxT("\t") ), - POPUP_MENU_FN( OnMoveTrack ) ); + POPUP_MENU_FN( OnMoveTrack ), enableIfCanMove(false) ); AppendItem( "Top", OnMoveTopID, XO("Move Track to &Top").Join( @@ -163,7 +160,7 @@ BEGIN_POPUP_MENU(TrackMenuTable) GetKeyFromName(wxT("TrackMoveTop")).GET() ), wxT("\t") ), - POPUP_MENU_FN( OnMoveTrack ) ); + POPUP_MENU_FN( OnMoveTrack ), enableIfCanMove(true) ); AppendItem( "Bottom", OnMoveBottomID, XO("Move Track to &Bottom").Join( @@ -173,7 +170,7 @@ BEGIN_POPUP_MENU(TrackMenuTable) GetKeyFromName(wxT("TrackMoveBottom")).GET() ), wxT("\t") ), - POPUP_MENU_FN( OnMoveTrack ) ); + POPUP_MENU_FN( OnMoveTrack ), enableIfCanMove(false) ); EndSection(); END_POPUP_MENU() diff --git a/src/widgets/PopupMenuTable.cpp b/src/widgets/PopupMenuTable.cpp index a157ef828..1f09f4901 100644 --- a/src/widgets/PopupMenuTable.cpp +++ b/src/widgets/PopupMenuTable.cpp @@ -80,7 +80,6 @@ void PopupMenuBuilder::DoBeginGroup( Registry::GroupItem &item, const Path &path void PopupMenuBuilder::DoEndGroup( Registry::GroupItem &item, const Path &path ) { if ( auto pItem = dynamic_cast(&item) ) { - pItem->table.InitMenu( mMenu ); if ( !pItem->caption.empty() ) { auto subMenu = std::move( mMenus.back() ); mMenus.pop_back(); @@ -118,6 +117,9 @@ void PopupMenuBuilder::DoVisit( Registry::SingleItem &item, const Path &path ) // redundant pEntry->handler.InitUserData( mpUserData ); + if ( pEntry->init ) + pEntry->init( pEntry->handler, *mMenu, pEntry->id ); + mMenu->pParent->Bind( wxEVT_COMMAND_MENU_SELECTED, pEntry->func, &pEntry->handler, pEntry->id); } @@ -146,10 +148,11 @@ void PopupMenuTable::ExtendMenu( wxMenu &menu, PopupMenuTable &table ) void PopupMenuTable::Append( const Identifier &stringId, PopupMenuTableEntry::Type type, int id, - const TranslatableString &string, wxCommandEventFunction memFn) + const TranslatableString &string, wxCommandEventFunction memFn, + const PopupMenuTableEntry::InitFunction &init ) { mStack.back()->items.push_back( std::make_unique( - stringId, type, id, string, memFn, *this + stringId, type, id, string, memFn, *this, init ) ); } @@ -200,10 +203,6 @@ void PopupMenu::Disconnect() } } -void PopupMenuHandler::InitMenu(wxMenu *) -{ -} - // static std::unique_ptr PopupMenuTable::BuildMenu ( wxEvtHandler *pParent, PopupMenuTable *pTable, void *pUserData ) diff --git a/src/widgets/PopupMenuTable.h b/src/widgets/PopupMenuTable.h index 0113ac97c..1fe4a5df8 100644 --- a/src/widgets/PopupMenuTable.h +++ b/src/widgets/PopupMenuTable.h @@ -20,6 +20,7 @@ tables, and automatically attaches and detaches the event handlers. class wxCommandEvent; class wxString; +#include #include #include // to inherit wxMenu #include "../MemoryX.h" @@ -33,22 +34,27 @@ class PopupMenuTable; struct PopupMenuTableEntry : Registry::SingleItem { enum Type { Item, RadioItem, CheckItem }; + using InitFunction = + std::function< void( PopupMenuHandler &handler, wxMenu &menu, int id ) >; Type type; int id; TranslatableString caption; wxCommandEventFunction func; PopupMenuHandler &handler; + InitFunction init; PopupMenuTableEntry( const Identifier &stringId, Type type_, int id_, const TranslatableString &caption_, - wxCommandEventFunction func_, PopupMenuHandler &handler_ ) + wxCommandEventFunction func_, PopupMenuHandler &handler_, + InitFunction init_ ) : SingleItem{ stringId } , type(type_) , id(id_) , caption(caption_) , func(func_) , handler( handler_ ) + , init( init_ ) {} ~PopupMenuTableEntry() override; @@ -84,11 +90,6 @@ public: // May be called more than once before the menu opens. virtual void InitUserData(void *pUserData) = 0; - // Called when the menu is about to pop up. - // Your chance to enable and disable items. - // Default implementation does nothing. - virtual void InitMenu(wxMenu *pMenu); - // Called when menu is destroyed. // May be called more than once. virtual void DestroyMenu() = 0; @@ -131,19 +132,26 @@ protected: // To be used in implementations of Populate(): void Append( const Identifier &stringId, PopupMenuTableEntry::Type type, int id, - const TranslatableString &string, wxCommandEventFunction memFn); + const TranslatableString &string, wxCommandEventFunction memFn, + // This callback might check or disable a menu item: + const PopupMenuTableEntry::InitFunction &init ); void AppendItem( const Identifier &stringId, int id, - const TranslatableString &string, wxCommandEventFunction memFn ) - { Append( stringId, PopupMenuTableEntry::Item, id, string, memFn ); } + const TranslatableString &string, wxCommandEventFunction memFn, + // This callback might check or disable a menu item: + const PopupMenuTableEntry::InitFunction &init = {} ) + { Append( stringId, PopupMenuTableEntry::Item, id, string, memFn, init ); } void AppendRadioItem( const Identifier &stringId, int id, - const TranslatableString &string, wxCommandEventFunction memFn ) - { Append( stringId, PopupMenuTableEntry::RadioItem, id, string, memFn ); } + const TranslatableString &string, wxCommandEventFunction memFn, + // This callback might check or disable a menu item: + const PopupMenuTableEntry::InitFunction &init = {} ) + { Append( stringId, PopupMenuTableEntry::RadioItem, id, string, memFn, init ); } void AppendCheckItem( const Identifier &stringId, int id, - const TranslatableString &string, wxCommandEventFunction memFn ) - { Append( stringId, PopupMenuTableEntry::CheckItem, id, string, memFn ); } + const TranslatableString &string, wxCommandEventFunction memFn, + const PopupMenuTableEntry::InitFunction &init = {} ) + { Append( stringId, PopupMenuTableEntry::CheckItem, id, string, memFn, init ); } void BeginSection( const Identifier &name ); void EndSection(); @@ -166,7 +174,6 @@ which inherits from PopupMenuTable, DECLARE_POPUP_MENU(MyTable); virtual void InitUserData(void *pUserData); -virtual void InitMenu(wxMenu *pMenu); virtual void DestroyMenu(); Then in MyTable.cpp, @@ -177,11 +184,6 @@ void MyTable::InitUserData(void *pUserData) auto pData = static_cast(pUserData); } -void MyTable::InitMenu(wxMenu *pMenu) -{ - // enable or disable menu items -} - void MyTable::DestroyMenu() { // Do cleanup @@ -192,7 +194,14 @@ BEGIN_POPUP_MENU(MyTable) // you only need a sequence of calls: AppendItem("Cut", - OnCutSelectedTextID, XO("Cu&t"), POPUP_MENU_FN( OnCutSelectedText ) ); + OnCutSelectedTextID, XO("Cu&t"), POPUP_MENU_FN( OnCutSelectedText ), + // optional argument: + [](PopupMenuHandler &handler, wxMenu &menu, int id) + { + auto data = static_cast( handler ).pData; + // maybe enable or disable the menu item + } + ); // etc. END_POPUP_MENU()