audacia/src/menus/ViewMenus.cpp

563 lines
18 KiB
C++

#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 <wx/scrolbar.h>
// 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<AudioTrack>()
- [](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