#include "../Audacity.h" #include "../Experimental.h" #include "../CommonCommandFlags.h" #include "../HistoryWindow.h" #include "../LyricsWindow.h" #include "../Menus.h" #include "../MixerBoard.h" #include "../Prefs.h" #include "../Project.h" #include "../ProjectHistory.h" #include "../ProjectSettings.h" #include "../ProjectWindow.h" #include "../Track.h" #include "../TrackInfo.h" #include "../TrackPanel.h" #include "../UndoManager.h" #include "../ViewInfo.h" #include "../commands/CommandContext.h" #include "../commands/CommandManager.h" #include "../prefs/GUIPrefs.h" #include "../prefs/TracksPrefs.h" #include "../tracks/ui/TrackView.h" #ifdef EXPERIMENTAL_EFFECTS_RACK #include "../effects/EffectUI.h" #endif #include // private helper classes and functions namespace { AudacityProject::AttachedWindows::RegisteredFactory sMixerBoardKey{ []( AudacityProject &parent ) -> wxWeakRef< wxWindow > { return safenew MixerBoardFrame( &parent ); } }; AudacityProject::AttachedWindows::RegisteredFactory sHistoryWindowKey{ []( AudacityProject &parent ) -> wxWeakRef< wxWindow > { auto &undoManager = UndoManager::Get( parent ); return safenew HistoryDialog( &parent, &undoManager ); } }; AudacityProject::AttachedWindows::RegisteredFactory sLyricsWindowKey{ []( AudacityProject &parent ) -> wxWeakRef< wxWindow > { return safenew LyricsWindow( &parent ); } }; double GetZoomOfSelection( const AudacityProject &project ) { auto &viewInfo = ViewInfo::Get( project ); auto &window = ProjectWindow::Get( project ); const double lowerBound = std::max(viewInfo.selectedRegion.t0(), window.ScrollingLowerBoundTime()); const double denom = viewInfo.selectedRegion.t1() - lowerBound; if (denom <= 0.0) return viewInfo.GetZoom(); // LL: The "-1" is just a hack to get around an issue where zooming to // selection doesn't actually get the entire selected region within the // visible area. This causes a problem with scrolling at end of playback // where the selected region may be scrolled off the left of the screen. // I know this isn't right, but until the real rounding or 1-off issue is // found, this will have to work. // PRL: Did I fix this? I am not sure, so I leave the hack in place. // Fixes might have resulted from commits // 1b8f44d0537d987c59653b11ed75a842b48896ea and // e7c7bb84a966c3b3cc4b3a9717d5f247f25e7296 auto width = viewInfo.GetTracksUsableWidth(); return (width - 1) / denom; } double GetZoomOfPreset( const AudacityProject &project, int preset ) { // Sets a limit on how far we will zoom out as a factor over zoom to fit. const double maxZoomOutFactor = 4.0; // Sets how many pixels we allow for one uint, such as seconds. const double pixelsPerUnit = 5.0; double result = 1.0; auto &window = ProjectWindow::Get( project ); double zoomToFit = window.GetZoomOfToFit(); using namespace WaveTrackViewConstants; switch( preset ){ default: case kZoomDefault: result = ZoomInfo::GetDefaultZoom(); break; case kZoomToFit: result = zoomToFit; break; case kZoomToSelection: result = GetZoomOfSelection( project ); break; case kZoomMinutes: result = pixelsPerUnit * 1.0/60; break; case kZoomSeconds: result = pixelsPerUnit * 1.0; break; case kZoom5ths: result = pixelsPerUnit * 5.0; break; case kZoom10ths: result = pixelsPerUnit * 10.0; break; case kZoom20ths: result = pixelsPerUnit * 20.0; break; case kZoom50ths: result = pixelsPerUnit * 50.0; break; case kZoom100ths: result = pixelsPerUnit * 100.0; break; case kZoom500ths: result = pixelsPerUnit * 500.0; break; case kZoomMilliSeconds: result = pixelsPerUnit * 1000.0; break; case kZoomSamples: result = 44100.0; break; case kZoom4To1: result = 44100.0 * 4; break; case kMaxZoom: result = ZoomInfo::GetMaxZoom(); break; }; if( result < (zoomToFit/maxZoomOutFactor) ) result = zoomToFit / maxZoomOutFactor; return result; } } namespace { void DoZoomFitV(AudacityProject &project) { auto &viewInfo = ViewInfo::Get( project ); auto &tracks = TrackList::Get( project ); // Only nonminimized audio tracks will be resized auto range = tracks.Any() - [](const Track *pTrack){ return TrackView::Get( *pTrack ).GetMinimized(); }; auto count = range.size(); if (count == 0) return; // Find total height to apportion auto height = viewInfo.GetHeight(); height -= 28; // The height of minimized and non-audio tracks cannot be apportioned height -= tracks.Any().sum( TrackView::GetTrackHeight ) - range.sum( TrackView::GetTrackHeight ); // Give each resized track the average of the remaining height height = height / count; height = std::max( (int)TrackInfo::MinimumTrackHeight(), height ); for (auto t : range) TrackView::Get( *t ).SetHeight(height); } } namespace ViewActions { // Menu handler functions struct Handler final : CommandHandlerObject // MUST be the first base class! , ClientData::Base { void OnZoomIn(const CommandContext &context) { auto &project = context.project; auto &window = ProjectWindow::Get( project ); window.ZoomInByFactor( 2.0 ); } void OnZoomNormal(const CommandContext &context) { auto &project = context.project; auto &trackPanel = TrackPanel::Get( project ); auto &window = ProjectWindow::Get( project ); window.Zoom(ZoomInfo::GetDefaultZoom()); trackPanel.Refresh(false); } void OnZoomOut(const CommandContext &context) { auto &project = context.project; auto &window = ProjectWindow::Get( project ); window.ZoomOutByFactor( 1 /2.0 ); } void OnZoomSel(const CommandContext &context) { auto &project = context.project; auto &selectedRegion = ViewInfo::Get( project ).selectedRegion; auto &window = ProjectWindow::Get( project ); window.Zoom( GetZoomOfSelection( project ) ); window.TP_ScrollWindow(selectedRegion.t0()); } void OnZoomToggle(const CommandContext &context) { auto &project = context.project; auto &viewInfo = ViewInfo::Get( project ); auto &trackPanel = TrackPanel::Get( project ); auto &window = ProjectWindow::Get( project ); // const double origLeft = viewInfo.h; // const double origWidth = viewInfo.GetScreenEndTime() - origLeft; // Choose the zoom that is most different to the current zoom. double Zoom1 = GetZoomOfPreset( project, TracksPrefs::Zoom1Choice() ); double Zoom2 = GetZoomOfPreset( project, TracksPrefs::Zoom2Choice() ); double Z = viewInfo.GetZoom();// Current Zoom. double ChosenZoom = fabs(log(Zoom1 / Z)) > fabs(log( Z / Zoom2)) ? Zoom1:Zoom2; window.Zoom(ChosenZoom); trackPanel.Refresh(false); // const double newWidth = GetScreenEndTime() - viewInfo.h; // const double newh = origLeft + (origWidth - newWidth) / 2; // TP_ScrollWindow(newh); } void OnZoomFit(const CommandContext &context) { auto &project = context.project; auto &window = ProjectWindow::Get( project ); window.DoZoomFit(); } void OnZoomFitV(const CommandContext &context) { auto &project = context.project; auto &window = ProjectWindow::Get( project ); DoZoomFitV(project); window.GetVerticalScrollBar().SetThumbPosition(0); ProjectHistory::Get( project ).ModifyState(true); } void OnAdvancedVZoom(const CommandContext &context) { auto &project = context.project; auto &commandManager = CommandManager::Get( project ); bool checked = !gPrefs->Read(wxT("/GUI/VerticalZooming"), 0L); gPrefs->Write(wxT("/GUI/VerticalZooming"), checked); gPrefs->Flush(); commandManager.Check(wxT("AdvancedVZoom"), checked); MenuCreator::RebuildAllMenuBars(); } void OnCollapseAllTracks(const CommandContext &context) { auto &project = context.project; auto &tracks = TrackList::Get( project ); auto &window = ProjectWindow::Get( project ); for (auto t : tracks.Any()) TrackView::Get( *t ).SetMinimized(true); ProjectHistory::Get( project ).ModifyState(true); } void OnExpandAllTracks(const CommandContext &context) { auto &project = context.project; auto &tracks = TrackList::Get( project ); auto &window = ProjectWindow::Get( project ); for (auto t : tracks.Any()) TrackView::Get( *t ).SetMinimized(false); ProjectHistory::Get( project ).ModifyState(true); } void OnGoSelStart(const CommandContext &context) { auto &project = context.project; auto &viewInfo = ViewInfo::Get( project ); auto &selectedRegion = viewInfo.selectedRegion; auto &window = ProjectWindow::Get( project ); if (selectedRegion.isPoint()) return; window.TP_ScrollWindow( selectedRegion.t0() - ((viewInfo.GetScreenEndTime() - viewInfo.h) / 2)); } void OnGoSelEnd(const CommandContext &context) { auto &project = context.project; auto &viewInfo = ViewInfo::Get( project ); auto &selectedRegion = viewInfo.selectedRegion; auto &window = ProjectWindow::Get( project ); if (selectedRegion.isPoint()) return; window.TP_ScrollWindow( selectedRegion.t1() - ((viewInfo.GetScreenEndTime() - viewInfo.h) / 2)); } void OnHistory(const CommandContext &context) { auto &project = context.project; auto historyWindow = &project.AttachedWindows::Get( sHistoryWindowKey ); historyWindow->Show(); historyWindow->Raise(); } void OnKaraoke(const CommandContext &context) { auto &project = context.project; auto lyricsWindow = &project.AttachedWindows::Get( sLyricsWindowKey ); lyricsWindow->Show(); lyricsWindow->Raise(); } void OnMixerBoard(const CommandContext &context) { auto &project = context.project; auto mixerBoardFrame = &project.AttachedWindows::Get( sMixerBoardKey ); mixerBoardFrame->Show(); mixerBoardFrame->Raise(); mixerBoardFrame->SetFocus(); } void OnShowExtraMenus(const CommandContext &context) { auto &project = context.project; auto &commandManager = CommandManager::Get( project ); bool checked = !gPrefs->Read(wxT("/GUI/ShowExtraMenus"), 0L); gPrefs->Write(wxT("/GUI/ShowExtraMenus"), checked); gPrefs->Flush(); commandManager.Check(wxT("ShowExtraMenus"), checked); MenuCreator::RebuildAllMenuBars(); } void OnShowClipping(const CommandContext &context) { auto &project = context.project; auto &commandManager = CommandManager::Get( project ); auto &trackPanel = TrackPanel::Get( project ); bool checked = !gPrefs->Read(wxT("/GUI/ShowClipping"), 0L); gPrefs->Write(wxT("/GUI/ShowClipping"), checked); gPrefs->Flush(); commandManager.Check(wxT("ShowClipping"), checked); wxTheApp->AddPendingEvent(wxCommandEvent{ EVT_PREFS_UPDATE, ShowClippingPrefsID() }); trackPanel.Refresh(false); } #if defined(EXPERIMENTAL_EFFECTS_RACK) void OnShowEffectsRack(const CommandContext &context ) { auto &rack = EffectRack::Get( context.project ); rack.Show( !rack.IsShown() ); } #endif // Not a menu item, but a listener for events void OnUndoPushed( wxCommandEvent &evt ) { evt.Skip(); const auto &settings = ProjectSettings::Get( mProject ); if (settings.GetTracksFitVerticallyZoomed()) DoZoomFitV( mProject ); } Handler( AudacityProject &project ) : mProject{ project } { mProject.Bind( EVT_UNDO_PUSHED, &Handler::OnUndoPushed, this ); } ~Handler() { mProject.Unbind( EVT_UNDO_PUSHED, &Handler::OnUndoPushed, this ); } Handler( const Handler & ) PROHIBITED; Handler &operator=( const Handler & ) PROHIBITED; AudacityProject &mProject; }; // struct Handler } // namespace // Handler needs a back-reference to the project, so needs a factory registered // with AudacityProject. static const AudacityProject::AttachedObjects::RegisteredFactory key{ []( AudacityProject &project ) { return std::make_unique< ViewActions::Handler >( project ); } }; static CommandHandlerObject &findCommandHandler(AudacityProject &project) { return project.AttachedObjects::Get< ViewActions::Handler >( key ); }; // Menu definitions #define FN(X) (& ViewActions::Handler :: X) MenuTable::BaseItemSharedPtr ToolbarsMenu(); // Under /MenuBar MenuTable::BaseItemSharedPtr ViewMenu() { using namespace MenuTable; using Options = CommandManager::Options; static const auto checkOff = Options{}.CheckState( false ); static BaseItemSharedPtr menu{ ( FinderScope{ findCommandHandler }, Menu( wxT("View"), XO("&View"), Section( "Basic", Menu( wxT("Zoom"), XO("&Zoom"), Section( "", Command( wxT("ZoomIn"), XXO("Zoom &In"), FN(OnZoomIn), ZoomInAvailableFlag(), wxT("Ctrl+1") ), Command( wxT("ZoomNormal"), XXO("Zoom &Normal"), FN(OnZoomNormal), TracksExistFlag(), wxT("Ctrl+2") ), Command( wxT("ZoomOut"), XXO("Zoom &Out"), FN(OnZoomOut), ZoomOutAvailableFlag(), wxT("Ctrl+3") ), Command( wxT("ZoomSel"), XXO("&Zoom to Selection"), FN(OnZoomSel), TimeSelectedFlag(), wxT("Ctrl+E") ), Command( wxT("ZoomToggle"), XXO("Zoom &Toggle"), FN(OnZoomToggle), TracksExistFlag(), wxT("Shift+Z") ) ), Section( "", Command( wxT("AdvancedVZoom"), XXO("Advanced &Vertical Zooming"), FN(OnAdvancedVZoom), AlwaysEnabledFlag, Options{}.CheckTest( wxT("/GUI/VerticalZooming"), false ) ) ) ), Menu( wxT("TrackSize"), XO("T&rack Size"), Command( wxT("FitInWindow"), XXO("&Fit to Width"), FN(OnZoomFit), TracksExistFlag(), wxT("Ctrl+F") ), Command( wxT("FitV"), XXO("Fit to &Height"), FN(OnZoomFitV), TracksExistFlag(), wxT("Ctrl+Shift+F") ), Command( wxT("CollapseAllTracks"), XXO("&Collapse All Tracks"), FN(OnCollapseAllTracks), TracksExistFlag(), wxT("Ctrl+Shift+C") ), Command( wxT("ExpandAllTracks"), XXO("E&xpand Collapsed Tracks"), FN(OnExpandAllTracks), TracksExistFlag(), wxT("Ctrl+Shift+X") ) ), Menu( wxT("SkipTo"), XO("Sk&ip to"), Command( wxT("SkipSelStart"), XXO("Selection Sta&rt"), FN(OnGoSelStart), TimeSelectedFlag(), Options{ wxT("Ctrl+["), XO("Skip to Selection Start") } ), Command( wxT("SkipSelEnd"), XXO("Selection En&d"), FN(OnGoSelEnd), TimeSelectedFlag(), Options{ wxT("Ctrl+]"), XO("Skip to Selection End") } ) ) ), Section( "Windows", // History window should be available either for UndoAvailableFlag // or RedoAvailableFlag, // but we can't make the AddItem flags and mask have both, // because they'd both have to be true for the // command to be enabled. // If user has Undone the entire stack, RedoAvailableFlag is on // but UndoAvailableFlag is off. // If user has done things but not Undone anything, // RedoAvailableFlag is off but UndoAvailableFlag is on. // So in either of those cases, // (AudioIONotBusyFlag | UndoAvailableFlag | RedoAvailableFlag) mask // would fail. // The only way to fix this in the current architecture // is to hack in special cases for RedoAvailableFlag // in AudacityProject::UpdateMenus() (ugly) // and CommandManager::HandleCommandEntry() (*really* ugly -- // shouldn't know about particular command names and flags). // Here's the hack that would be necessary in // AudacityProject::UpdateMenus(), if somebody decides to do it: // // Because EnableUsingFlags requires all the flag bits match the // // corresponding mask bits, // // "UndoHistory" specifies only // // AudioIONotBusyFlag | UndoAvailableFlag, because that // // covers the majority of cases where it should be enabled. // // If history is not empty but we've Undone the whole stack, // // we also want to enable, // // to show the Redo's on stack. // // "UndoHistory" might already be enabled, // // but add this check for RedoAvailableFlag. // if (flags & RedoAvailableFlag) // GetCommandManager()->Enable(wxT("UndoHistory"), true); // So for now, enable the command regardless of stack. // It will just show empty sometimes. // FOR REDESIGN, // clearly there are some limitations with the flags/mask bitmaps. /* i18n-hint: Clicking this menu item shows the various editing steps that have been taken.*/ Command( wxT("UndoHistory"), XXO("&History..."), FN(OnHistory), AudioIONotBusyFlag() ), Command( wxT("Karaoke"), XXO("&Karaoke..."), FN(OnKaraoke), LabelTracksExistFlag() ), Command( wxT("MixerBoard"), XXO("&Mixer Board..."), FN(OnMixerBoard), PlayableTracksExistFlag() ) ), Section( "", ////////////////////////////////////////////////////////////////////////// ToolbarsMenu() ), Section( "Other", Command( wxT("ShowExtraMenus"), XXO("&Extra Menus (on/off)"), FN(OnShowExtraMenus), AlwaysEnabledFlag, Options{}.CheckTest( wxT("/GUI/ShowExtraMenus"), false ) ), Command( wxT("ShowClipping"), XXO("&Show Clipping (on/off)"), FN(OnShowClipping), AlwaysEnabledFlag, Options{}.CheckTest( wxT("/GUI/ShowClipping"), false ) ) #if defined(EXPERIMENTAL_EFFECTS_RACK) , Command( wxT("ShowEffectsRack"), XXO("Show Effects Rack"), FN(OnShowEffectsRack), AlwaysEnabledFlag, checkOff ) #endif ) ) ) }; return menu; } #undef FN