mirror of
https://github.com/cookiengineer/audacity
synced 2025-06-18 00:50:05 +02:00
4536 lines
135 KiB
C++
4536 lines
135 KiB
C++
/**********************************************************************
|
|
|
|
Audacity: A Digital Audio Editor
|
|
|
|
Menus.cpp
|
|
|
|
Dominic Mazzoni
|
|
Brian Gunlogson
|
|
et al.
|
|
|
|
*******************************************************************//**
|
|
|
|
\file Menus.cpp
|
|
\brief Functions that provide most of the menu actions.
|
|
|
|
This file implements the method that creates the menu bar, plus
|
|
most of the methods that get called when you select an item
|
|
from a menu.
|
|
|
|
*//****************************************************************//**
|
|
|
|
\class MenuCommandHandler
|
|
\brief MenuCommandHandler contains many command handlers for individual
|
|
menu items.
|
|
|
|
*//****************************************************************//**
|
|
|
|
\class MenuCreator
|
|
\brief MenuCreator is responsible for creating the main menu bar.
|
|
|
|
*//****************************************************************//**
|
|
|
|
\class MenuManager
|
|
\brief MenuManager handles updates to menu state.
|
|
|
|
*//*******************************************************************/
|
|
|
|
#include "Audacity.h"
|
|
#include "Menus.h"
|
|
#include "commands/CommandManager.h"
|
|
#include "commands/CommandContext.h"
|
|
|
|
#include <cfloat>
|
|
#include <iterator>
|
|
#include <algorithm>
|
|
#include <limits>
|
|
#include <math.h>
|
|
|
|
|
|
#include <wx/defs.h>
|
|
#include <wx/docview.h>
|
|
#include <wx/filedlg.h>
|
|
#include <wx/textfile.h>
|
|
#include <wx/textdlg.h>
|
|
#include <wx/progdlg.h>
|
|
#include <wx/scrolbar.h>
|
|
#include <wx/ffile.h>
|
|
#include <wx/statusbr.h>
|
|
#include <wx/utils.h>
|
|
|
|
#include "FreqWindow.h"
|
|
#include "effects/Contrast.h"
|
|
#include "TrackPanel.h"
|
|
|
|
#include "effects/EffectManager.h"
|
|
|
|
#include "AudacityApp.h"
|
|
#include "AudacityLogger.h"
|
|
#include "AudioIO.h"
|
|
#include "Dependencies.h"
|
|
#include "float_cast.h"
|
|
#include "LabelTrack.h"
|
|
#include "import/ImportRaw.h"
|
|
#include "prefs/PrefsDialog.h"
|
|
#include "prefs/PlaybackPrefs.h"
|
|
#include "ShuttleGui.h"
|
|
#include "LyricsWindow.h"
|
|
#include "MixerBoard.h"
|
|
#include "Project.h"
|
|
#include "Internat.h"
|
|
#include "FileFormats.h"
|
|
#include "ModuleManager.h"
|
|
#include "PluginManager.h"
|
|
#include "Prefs.h"
|
|
#ifdef USE_MIDI
|
|
#include "NoteTrack.h"
|
|
#endif // USE_MIDI
|
|
#include "TimeTrack.h"
|
|
#include "Mix.h"
|
|
#include "AboutDialog.h"
|
|
#include "Benchmark.h"
|
|
#include "Screenshot.h"
|
|
#include "ondemand/ODManager.h"
|
|
|
|
#include "BatchProcessDialog.h"
|
|
#include "prefs/BatchPrefs.h"
|
|
|
|
#include "toolbars/ToolManager.h"
|
|
#include "toolbars/ControlToolBar.h"
|
|
#include "toolbars/EditToolBar.h"
|
|
#include "toolbars/DeviceToolBar.h"
|
|
#include "toolbars/MixerToolBar.h"
|
|
|
|
#include "tracks/ui/SelectHandle.h"
|
|
|
|
#include "widgets/LinkingHtmlWindow.h"
|
|
|
|
#include "Experimental.h"
|
|
#include "PlatformCompatibility.h"
|
|
#include "FileNames.h"
|
|
|
|
#include "SplashDialog.h"
|
|
#include "widgets/HelpSystem.h"
|
|
|
|
#include "UndoManager.h"
|
|
#include "WaveTrack.h"
|
|
|
|
#if defined(EXPERIMENTAL_CRASH_REPORT)
|
|
#include <wx/debugrpt.h>
|
|
#endif
|
|
|
|
#ifdef EXPERIMENTAL_SCOREALIGN
|
|
#include "effects/ScoreAlignDialog.h"
|
|
#include "audioreader.h"
|
|
#include "scorealign.h"
|
|
#include "scorealign-glue.h"
|
|
#endif /* EXPERIMENTAL_SCOREALIGN */
|
|
|
|
#include "prefs/TracksPrefs.h"
|
|
|
|
#include "widgets/Meter.h"
|
|
#include "widgets/ErrorDialog.h"
|
|
#include "./commands/AudacityCommand.h"
|
|
|
|
static MenuTable::BaseItemPtrs PopulateMacrosMenu(
|
|
CommandFlag flags );
|
|
static MenuTable::BaseItemPtrs PopulateEffectsMenu(
|
|
EffectType type,
|
|
CommandFlag batchflags,
|
|
CommandFlag realflags);
|
|
static void AddEffectMenuItems(
|
|
MenuTable::BaseItemPtrs &table,
|
|
std::vector<const PluginDescriptor*> & plugs,
|
|
CommandFlag batchflags,
|
|
CommandFlag realflags,
|
|
bool isDefault);
|
|
static void AddEffectMenuItemGroup(
|
|
MenuTable::BaseItemPtrs &table,
|
|
const wxArrayString & names,
|
|
const std::vector<bool> &vHasDialog,
|
|
const PluginIDList & plugs,
|
|
const std::vector<CommandFlag> & flags,
|
|
bool isDefault);
|
|
|
|
constexpr size_t kAlignLabelsCount = 5;
|
|
|
|
static const AudacityProject::RegisteredAttachedObjectFactory factory{ []{
|
|
return std::make_unique< MenuCommandHandler >();
|
|
} };
|
|
|
|
PrefsListener::~PrefsListener()
|
|
{
|
|
}
|
|
|
|
void PrefsListener::UpdatePrefs()
|
|
{
|
|
}
|
|
|
|
MenuCommandHandler &GetMenuCommandHandler(AudacityProject &project)
|
|
{
|
|
return static_cast<MenuCommandHandler&>(
|
|
project.GetAttachedObject( factory ) );
|
|
}
|
|
|
|
MenuManager &GetMenuManager(AudacityProject &project)
|
|
{ return *project.mMenuManager; }
|
|
|
|
MenuCommandHandler::MenuCommandHandler()
|
|
{
|
|
}
|
|
|
|
MenuCommandHandler::~MenuCommandHandler()
|
|
{
|
|
}
|
|
|
|
MenuCreator::MenuCreator(){
|
|
}
|
|
|
|
MenuCreator::~MenuCreator()
|
|
{
|
|
}
|
|
|
|
enum {
|
|
kAlignStartZero = 0,
|
|
kAlignStartSelStart,
|
|
kAlignStartSelEnd,
|
|
kAlignEndSelStart,
|
|
kAlignEndSelEnd,
|
|
// The next two are only in one subMenu, so more easily handled at the end.
|
|
kAlignEndToEnd,
|
|
kAlignTogether
|
|
};
|
|
|
|
#include "commands/ScreenshotCommand.h"
|
|
|
|
|
|
//
|
|
// Effects menu arrays
|
|
//
|
|
static bool SortEffectsByName(const PluginDescriptor *a, const PluginDescriptor *b)
|
|
{
|
|
auto akey = a->GetSymbol().Translation();
|
|
auto bkey = b->GetSymbol().Translation();
|
|
|
|
akey += a->GetPath();
|
|
bkey += b->GetPath();
|
|
|
|
return akey.CmpNoCase(bkey) < 0;
|
|
}
|
|
|
|
static bool SortEffectsByPublisher(const PluginDescriptor *a, const PluginDescriptor *b)
|
|
{
|
|
auto &em = EffectManager::Get();
|
|
auto akey = em.GetVendorName(a->GetID());
|
|
auto bkey = em.GetVendorName(b->GetID());
|
|
|
|
if (akey.IsEmpty())
|
|
{
|
|
akey = _("Uncategorized");
|
|
}
|
|
if (bkey.IsEmpty())
|
|
{
|
|
bkey = _("Uncategorized");
|
|
}
|
|
|
|
akey += a->GetSymbol().Translation();
|
|
bkey += b->GetSymbol().Translation();
|
|
|
|
akey += a->GetPath();
|
|
bkey += b->GetPath();
|
|
|
|
return akey.CmpNoCase(bkey) < 0;
|
|
}
|
|
|
|
static bool SortEffectsByPublisherAndName(const PluginDescriptor *a, const PluginDescriptor *b)
|
|
{
|
|
auto &em = EffectManager::Get();
|
|
auto akey = em.GetVendorName(a->GetID());
|
|
auto bkey = em.GetVendorName(b->GetID());
|
|
|
|
if (a->IsEffectDefault())
|
|
{
|
|
akey = wxEmptyString;
|
|
}
|
|
if (b->IsEffectDefault())
|
|
{
|
|
bkey = wxEmptyString;
|
|
}
|
|
|
|
akey += a->GetSymbol().Translation();
|
|
bkey += b->GetSymbol().Translation();
|
|
|
|
akey += a->GetPath();
|
|
bkey += b->GetPath();
|
|
|
|
return akey.CmpNoCase(bkey) < 0;
|
|
}
|
|
|
|
static bool SortEffectsByTypeAndName(const PluginDescriptor *a, const PluginDescriptor *b)
|
|
{
|
|
auto &em = EffectManager::Get();
|
|
auto akey = em.GetEffectFamilyName(a->GetID());
|
|
auto bkey = em.GetEffectFamilyName(b->GetID());
|
|
|
|
if (akey.IsEmpty())
|
|
{
|
|
akey = _("Uncategorized");
|
|
}
|
|
if (bkey.IsEmpty())
|
|
{
|
|
bkey = _("Uncategorized");
|
|
}
|
|
|
|
if (a->IsEffectDefault())
|
|
{
|
|
akey = wxEmptyString;
|
|
}
|
|
if (b->IsEffectDefault())
|
|
{
|
|
bkey = wxEmptyString;
|
|
}
|
|
|
|
akey += a->GetSymbol().Translation();
|
|
bkey += b->GetSymbol().Translation();
|
|
|
|
akey += a->GetPath();
|
|
bkey += b->GetPath();
|
|
|
|
return akey.CmpNoCase(bkey) < 0;
|
|
}
|
|
|
|
static bool SortEffectsByType(const PluginDescriptor *a, const PluginDescriptor *b)
|
|
{
|
|
auto &em = EffectManager::Get();
|
|
auto akey = em.GetEffectFamilyName(a->GetID());
|
|
auto bkey = em.GetEffectFamilyName(b->GetID());
|
|
|
|
if (akey.IsEmpty())
|
|
{
|
|
akey = _("Uncategorized");
|
|
}
|
|
if (bkey.IsEmpty())
|
|
{
|
|
bkey = _("Uncategorized");
|
|
}
|
|
|
|
akey += a->GetSymbol().Translation();
|
|
bkey += b->GetSymbol().Translation();
|
|
|
|
akey += a->GetPath();
|
|
bkey += b->GetPath();
|
|
|
|
return akey.CmpNoCase(bkey) < 0;
|
|
}
|
|
|
|
void MenuCommandHandler::UpdatePrefs()
|
|
{
|
|
gPrefs->Read(wxT("/GUI/CircularTrackNavigation"), &mCircularTrackNavigation,
|
|
false);
|
|
}
|
|
|
|
void MenuManager::UpdatePrefs()
|
|
{
|
|
bool bSelectAllIfNone;
|
|
gPrefs->Read(wxT("/GUI/SelectAllOnNone"), &bSelectAllIfNone, false);
|
|
// 0 is grey out, 1 is Autoselect, 2 is Give warnings.
|
|
#ifdef EXPERIMENTAL_DA
|
|
// DA warns or greys out.
|
|
mWhatIfNoSelection = bSelectAllIfNone ? 2 : 0;
|
|
#else
|
|
// Audacity autoselects or warns.
|
|
mWhatIfNoSelection = bSelectAllIfNone ? 1 : 2;
|
|
#endif
|
|
mStopIfWasPaused = true; // not configurable for now, but could be later.
|
|
}
|
|
|
|
namespace MenuTable {
|
|
|
|
BaseItem::~BaseItem() {}
|
|
|
|
ComputedItem::~ComputedItem() {}
|
|
|
|
GroupItem::GroupItem( BaseItemPtrs &&items_ )
|
|
: items{ std::move( items_ ) }
|
|
{
|
|
}
|
|
void GroupItem::AppendOne( BaseItemPtr&& ptr )
|
|
{
|
|
items.push_back( std::move( ptr ) );
|
|
}
|
|
GroupItem::~GroupItem() {}
|
|
|
|
MenuItem::MenuItem( const wxString &title_, BaseItemPtrs &&items_ )
|
|
: GroupItem{ std::move( items_ ) }, title{ title_ }
|
|
{
|
|
wxASSERT( !title.empty() );
|
|
}
|
|
MenuItem::~MenuItem() {}
|
|
|
|
ConditionalGroupItem::ConditionalGroupItem(
|
|
Condition condition_, BaseItemPtrs &&items_ )
|
|
: GroupItem{ std::move( items_ ) }, condition{ condition_ }
|
|
{
|
|
}
|
|
ConditionalGroupItem::~ConditionalGroupItem() {}
|
|
|
|
SeparatorItem::~SeparatorItem() {}
|
|
|
|
CommandItem::CommandItem(const wxString &name_,
|
|
const wxString &label_in_,
|
|
bool hasDialog_,
|
|
CommandHandlerFinder finder_,
|
|
CommandFunctorPointer callback_,
|
|
CommandFlag flags_,
|
|
const CommandManager::Options &options_)
|
|
: name{ name_ }, label_in{ label_in_ }, hasDialog{ hasDialog_ }
|
|
, finder{ finder_ }, callback{ callback_ }
|
|
, flags{ flags_ }, options{ options_ }
|
|
{}
|
|
CommandItem::~CommandItem() {}
|
|
|
|
CommandGroupItem::CommandGroupItem(const wxString &name_,
|
|
const IdentInterfaceSymbol items_[],
|
|
size_t nItems_,
|
|
CommandHandlerFinder finder_,
|
|
CommandFunctorPointer callback_,
|
|
CommandFlag flags_,
|
|
bool isEffect_)
|
|
: name{ name_ }, items{ items_, items_ + nItems_ }
|
|
, finder{ finder_ }, callback{ callback_ }
|
|
, flags{ flags_ }, isEffect{ isEffect_ }
|
|
{}
|
|
CommandGroupItem::~CommandGroupItem() {}
|
|
|
|
SpecialItem::~SpecialItem() {}
|
|
|
|
}
|
|
|
|
namespace {
|
|
|
|
void VisitItem( AudacityProject &project, MenuTable::BaseItem *pItem );
|
|
|
|
void VisitItems(
|
|
AudacityProject &project, const MenuTable::BaseItemPtrs &items )
|
|
{
|
|
for ( auto &pSubItem : items )
|
|
VisitItem( project, pSubItem.get() );
|
|
}
|
|
|
|
void VisitItem( AudacityProject &project, MenuTable::BaseItem *pItem )
|
|
{
|
|
if (!pItem)
|
|
return;
|
|
|
|
auto &manager = *project.GetCommandManager();
|
|
|
|
using namespace MenuTable;
|
|
if (const auto pComputed =
|
|
dynamic_cast<ComputedItem*>( pItem )) {
|
|
// TODO maybe? memo-ize the results of the function, but that requires
|
|
// invalidating the memo at the right times
|
|
auto result = pComputed->factory( project );
|
|
if (result)
|
|
// recursion
|
|
VisitItem( project, result.get() );
|
|
}
|
|
else
|
|
if (const auto pCommand =
|
|
dynamic_cast<CommandItem*>( pItem )) {
|
|
manager.AddItem(
|
|
pCommand->name, pCommand->label_in, pCommand->hasDialog,
|
|
pCommand->finder, pCommand->callback,
|
|
pCommand->flags, pCommand->options
|
|
);
|
|
}
|
|
else
|
|
if (const auto pCommandList =
|
|
dynamic_cast<CommandGroupItem*>( pItem ) ) {
|
|
manager.AddItemList(pCommandList->name,
|
|
pCommandList->items.data(), pCommandList->items.size(),
|
|
pCommandList->finder, pCommandList->callback,
|
|
pCommandList->flags, pCommandList->isEffect);
|
|
}
|
|
else
|
|
if (const auto pMenu =
|
|
dynamic_cast<MenuItem*>( pItem )) {
|
|
manager.BeginMenu( pMenu->title );
|
|
// recursion
|
|
VisitItems( project, pMenu->items );
|
|
manager.EndMenu();
|
|
}
|
|
else
|
|
if (const auto pConditionalGroup =
|
|
dynamic_cast<ConditionalGroupItem*>( pItem )) {
|
|
const auto flag = pConditionalGroup->condition();
|
|
if (!flag)
|
|
manager.BeginOccultCommands();
|
|
// recursion
|
|
VisitItems( project, pConditionalGroup->items );
|
|
if (!flag)
|
|
manager.EndOccultCommands();
|
|
}
|
|
else
|
|
if (const auto pGroup =
|
|
dynamic_cast<GroupItem*>( pItem )) {
|
|
// recursion
|
|
VisitItems( project, pGroup->items );
|
|
}
|
|
else
|
|
if (dynamic_cast<SeparatorItem*>( pItem )) {
|
|
manager.AddSeparator();
|
|
}
|
|
else
|
|
if (const auto pSpecial =
|
|
dynamic_cast<SpecialItem*>( pItem )) {
|
|
const auto pMenu = manager.CurrentMenu();
|
|
wxASSERT( pMenu );
|
|
pSpecial->fn( project, *pMenu );
|
|
}
|
|
else
|
|
wxASSERT( false );
|
|
}
|
|
|
|
}
|
|
|
|
/// CreateMenusAndCommands builds the menus, and also rebuilds them after
|
|
/// changes in configured preferences - for example changes in key-bindings
|
|
/// affect the short-cut key legend that appears beside each command,
|
|
|
|
// To supply the "finder" argument in AddItem calls
|
|
static CommandHandlerObject &findMenuCommandHandler(AudacityProject &project)
|
|
{ return GetMenuCommandHandler( project ); }
|
|
|
|
#define FN(X) findMenuCommandHandler, \
|
|
static_cast<CommandFunctorPointer>(& MenuCommandHandler :: X)
|
|
#define XXO(X) _(X), wxString{X}.Contains("...")
|
|
|
|
MenuTable::BaseItemPtr FileMenu( AudacityProject& );
|
|
|
|
MenuTable::BaseItemPtr EditMenu( AudacityProject& );
|
|
MenuTable::BaseItemPtr ExtraEditMenu( AudacityProject & );
|
|
|
|
MenuTable::BaseItemPtr SelectMenu( AudacityProject& );
|
|
MenuTable::BaseItemPtr ExtraSelectionMenu( AudacityProject & );
|
|
MenuTable::BaseItemPtr ExtraCursorMenu( AudacityProject & );
|
|
MenuTable::BaseItemPtr ExtraSeekMenu( AudacityProject & );
|
|
|
|
MenuTable::BaseItemPtr ExtraToolsMenu( AudacityProject & );
|
|
|
|
MenuTable::BaseItemPtr ViewMenu( AudacityProject& );
|
|
|
|
MenuTable::BaseItemPtr TransportMenu( AudacityProject& );
|
|
MenuTable::BaseItemPtr ExtraTransportMenu( AudacityProject & );
|
|
MenuTable::BaseItemPtr ExtraPlayAtSpeedMenu( AudacityProject & );
|
|
|
|
namespace {
|
|
MenuTable::BaseItemPtr TracksMenu( AudacityProject& );
|
|
MenuTable::BaseItemPtr GenerateMenu( AudacityProject& );
|
|
MenuTable::BaseItemPtr EffectMenu( AudacityProject& );
|
|
MenuTable::BaseItemPtr AnalyzeMenu( AudacityProject& );
|
|
MenuTable::BaseItemPtr ToolsMenu( AudacityProject& );
|
|
MenuTable::BaseItemPtr WindowMenu( AudacityProject& );
|
|
|
|
MenuTable::BaseItemPtr ExtraMenu( AudacityProject& );
|
|
MenuTable::BaseItemPtr ExtraMixerMenu( AudacityProject & );
|
|
MenuTable::BaseItemPtr ExtraDeviceMenu( AudacityProject & );
|
|
MenuTable::BaseItemPtr ExtraGlobalCommands( AudacityProject & );
|
|
MenuTable::BaseItemPtr ExtraFocusMenu( AudacityProject & );
|
|
MenuTable::BaseItemPtr ExtraTrackMenu( AudacityProject & );
|
|
MenuTable::BaseItemPtr ExtraScriptablesIMenu( AudacityProject & );
|
|
MenuTable::BaseItemPtr ExtraScriptablesIIMenu( AudacityProject & );
|
|
MenuTable::BaseItemPtr ExtraMiscItems( AudacityProject & );
|
|
|
|
MenuTable::BaseItemPtr HelpMenu( AudacityProject& );
|
|
}
|
|
|
|
// Tables of menu factories.
|
|
// TODO: devise a registration system instead.
|
|
static const std::shared_ptr<MenuTable::BaseItem> extraItems = MenuTable::Items(
|
|
ExtraTransportMenu
|
|
, ExtraToolsMenu
|
|
, ExtraMixerMenu
|
|
, ExtraEditMenu
|
|
, ExtraPlayAtSpeedMenu
|
|
, ExtraSeekMenu
|
|
, ExtraDeviceMenu
|
|
, ExtraSelectionMenu
|
|
|
|
, MenuTable::Separator()
|
|
|
|
, ExtraGlobalCommands
|
|
, ExtraFocusMenu
|
|
, ExtraCursorMenu
|
|
, ExtraTrackMenu
|
|
, ExtraScriptablesIMenu
|
|
, ExtraScriptablesIIMenu
|
|
, ExtraMiscItems
|
|
);
|
|
|
|
static const auto menuTree = MenuTable::Items(
|
|
FileMenu
|
|
, EditMenu
|
|
, SelectMenu
|
|
, ViewMenu
|
|
, TransportMenu
|
|
, TracksMenu
|
|
, GenerateMenu
|
|
, EffectMenu
|
|
, AnalyzeMenu
|
|
, ToolsMenu
|
|
, WindowMenu
|
|
, ExtraMenu
|
|
, HelpMenu
|
|
);
|
|
|
|
namespace {
|
|
|
|
MenuTable::BaseItemPtr TracksMenu( AudacityProject & )
|
|
{
|
|
// Tracks Menu (formerly Project Menu)
|
|
using namespace MenuTable;
|
|
using Options = CommandManager::Options;
|
|
|
|
return Menu( _("&Tracks"),
|
|
Menu( _("Add &New"),
|
|
Command( wxT("NewMonoTrack"), XXO("&Mono Track"), FN(OnNewWaveTrack),
|
|
AudioIONotBusyFlag, wxT("Ctrl+Shift+N") ),
|
|
Command( wxT("NewStereoTrack"), XXO("&Stereo Track"),
|
|
FN(OnNewStereoTrack), AudioIONotBusyFlag ),
|
|
Command( wxT("NewLabelTrack"), XXO("&Label Track"),
|
|
FN(OnNewLabelTrack), AudioIONotBusyFlag ),
|
|
Command( wxT("NewTimeTrack"), XXO("&Time Track"),
|
|
FN(OnNewTimeTrack), AudioIONotBusyFlag )
|
|
),
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
Separator(),
|
|
|
|
Menu( _("Mi&x"),
|
|
// Stereo to Mono is an oddball command that is also subject to control by the
|
|
// plug-in manager, as if an effect. Decide whether to show or hide it.
|
|
[](AudacityProject&) -> BaseItemPtr {
|
|
const PluginID ID =
|
|
EffectManager::Get().GetEffectByIdentifier(wxT("StereoToMono"));
|
|
const PluginDescriptor *plug = PluginManager::Get().GetPlugin(ID);
|
|
if (plug && plug->IsEnabled())
|
|
return Command( wxT("Stereo to Mono"),
|
|
XXO("Mix Stereo Down to &Mono"), FN(OnStereoToMono),
|
|
AudioIONotBusyFlag | StereoRequiredFlag |
|
|
WaveTracksSelectedFlag );
|
|
else
|
|
return {};
|
|
},
|
|
Command( wxT("MixAndRender"), XXO("Mi&x and Render"), FN(OnMixAndRender),
|
|
AudioIONotBusyFlag | WaveTracksSelectedFlag ),
|
|
Command( wxT("MixAndRenderToNewTrack"), XXO("Mix and Render to Ne&w Track"), FN(OnMixAndRenderToNewTrack),
|
|
AudioIONotBusyFlag | WaveTracksSelectedFlag, wxT("Ctrl+Shift+M") )
|
|
),
|
|
|
|
Command( wxT("Resample"), XXO("&Resample..."), FN(OnResample),
|
|
AudioIONotBusyFlag | WaveTracksSelectedFlag ),
|
|
|
|
Separator(),
|
|
|
|
Command( wxT("RemoveTracks"), XXO("Remo&ve Tracks"), FN(OnRemoveTracks),
|
|
AudioIONotBusyFlag | TracksSelectedFlag ),
|
|
|
|
Separator(),
|
|
|
|
Menu( _("M&ute/Unmute"),
|
|
Command( wxT("MuteAllTracks"), XXO("&Mute All Tracks"),
|
|
FN(OnMuteAllTracks), AudioIONotBusyFlag, wxT("Ctrl+U") ),
|
|
Command( wxT("UnmuteAllTracks"), XXO("&Unmute All Tracks"),
|
|
FN(OnUnmuteAllTracks), AudioIONotBusyFlag, wxT("Ctrl+Shift+U") )
|
|
),
|
|
|
|
Menu( _("&Pan"),
|
|
// As Pan changes are not saved on Undo stack,
|
|
// pan settings for all tracks
|
|
// in the project could very easily be lost unless we
|
|
// require the tracks to be selected.
|
|
Command( wxT("PanLeft"), XXO("&Left"), FN(OnPanLeft),
|
|
TracksSelectedFlag,
|
|
Options{}.LongName( _("Pan Left") ) ),
|
|
Command( wxT("PanRight"), XXO("&Right"), FN(OnPanRight),
|
|
TracksSelectedFlag,
|
|
Options{}.LongName( _("Pan Right") ) ),
|
|
Command( wxT("PanCenter"), XXO("&Center"), FN(OnPanCenter),
|
|
TracksSelectedFlag,
|
|
Options{}.LongName( _("Pan Center") ) )
|
|
),
|
|
|
|
Separator(),
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
Menu( _("&Align Tracks"), //_("Just Move Tracks"),
|
|
[]{
|
|
// Mutual alignment of tracks independent of selection or zero
|
|
static const IdentInterfaceSymbol alignLabelsNoSync[] = {
|
|
{ wxT("EndToEnd"), XO("&Align End to End") },
|
|
{ wxT("Together"), XO("Align &Together") },
|
|
};
|
|
return CommandGroup(wxT("Align"),
|
|
alignLabelsNoSync, 2u, FN(OnAlignNoSync),
|
|
AudioIONotBusyFlag | TracksSelectedFlag);
|
|
}(),
|
|
|
|
Separator(),
|
|
|
|
[]{
|
|
// Alignment commands using selection or zero
|
|
static const IdentInterfaceSymbol alignLabels[] = {
|
|
{ wxT("StartToZero"), XO("Start to &Zero") },
|
|
{ wxT("StartToSelStart"), XO("Start to &Cursor/Selection Start") },
|
|
{ wxT("StartToSelEnd"), XO("Start to Selection &End") },
|
|
{ wxT("EndToSelStart"), XO("End to Cu&rsor/Selection Start") },
|
|
{ wxT("EndToSelEnd"), XO("End to Selection En&d") },
|
|
};
|
|
static_assert(
|
|
kAlignLabelsCount == sizeof(alignLabels) / sizeof(alignLabels[0]),
|
|
"incorrect count"
|
|
);
|
|
return CommandGroup(wxT("Align"),
|
|
alignLabels, kAlignLabelsCount, FN(OnAlign),
|
|
AudioIONotBusyFlag | TracksSelectedFlag);
|
|
}(),
|
|
|
|
Separator(),
|
|
|
|
Command( wxT("MoveSelectionWithTracks"),
|
|
XXO("&Move Selection with Tracks (on/off)"),
|
|
FN(OnMoveSelectionWithTracks),
|
|
AlwaysEnabledFlag,
|
|
Options{}.CheckState(
|
|
gPrefs->Read(wxT("/GUI/MoveSelectionWithTracks"), 0L ) ) )
|
|
),
|
|
|
|
#if 0
|
|
// TODO: Can these labels be made clearer?
|
|
// Do we need this sub-menu at all?
|
|
Menu( _("Move Sele&ction and Tracks"), {
|
|
CommandGroup(wxT("AlignMove"), alignLabels, kAlignLabelsCount,
|
|
FN(OnAlignMoveSel), AudioIONotBusyFlag | TracksSelectedFlag),
|
|
} ),
|
|
#endif
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
#ifdef EXPERIMENTAL_SCOREALIGN
|
|
Command( wxT("ScoreAlign"), XXO("Synchronize MIDI with Audio"),
|
|
FN(OnScoreAlign),
|
|
AudioIONotBusyFlag | NoteTracksSelectedFlag | WaveTracksSelectedFlag ),
|
|
#endif // EXPERIMENTAL_SCOREALIGN
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
Menu( _("S&ort Tracks"),
|
|
Command( wxT("SortByTime"), XXO("By &Start Time"), FN(OnSortTime),
|
|
TracksExistFlag,
|
|
Options{}.LongName( _("Sort by Time") ) ),
|
|
Command( wxT("SortByName"), XXO("By &Name"), FN(OnSortName),
|
|
TracksExistFlag,
|
|
Options{}.LongName( _("Sort by Name") ) )
|
|
)
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
#ifdef EXPERIMENTAL_SYNC_LOCK
|
|
|
|
,
|
|
Separator(),
|
|
|
|
Command( wxT("SyncLock"), XXO("Sync-&Lock Tracks (on/off)"),
|
|
FN(OnSyncLock), AlwaysEnabledFlag,
|
|
Options{}.CheckState( gPrefs->Read(wxT("/GUI/SyncLockTracks"), 0L) ) )
|
|
|
|
#endif
|
|
);
|
|
}
|
|
|
|
MenuTable::BaseItemPtr GenerateMenu( AudacityProject & )
|
|
{
|
|
using namespace MenuTable;
|
|
// All of this is a bit hacky until we can get more things connected into
|
|
// the plugin manager...sorry! :-(
|
|
|
|
return Menu( _("&Generate"),
|
|
#ifdef EXPERIMENTAL_EFFECT_MANAGEMENT
|
|
Command( wxT("ManageGenerators"), XXO("Add / Remove Plug-ins..."),
|
|
FN(OnManageGenerators), AudioIONotBusyFlag ),
|
|
|
|
Separator(),
|
|
|
|
#endif
|
|
|
|
Items( PopulateEffectsMenu(
|
|
EffectTypeGenerate,
|
|
AudioIONotBusyFlag,
|
|
AudioIONotBusyFlag) )
|
|
);
|
|
}
|
|
|
|
MenuTable::BaseItemPtr EffectMenu( AudacityProject &project )
|
|
{
|
|
using namespace MenuTable;
|
|
// All of this is a bit hacky until we can get more things connected into
|
|
// the plugin manager...sorry! :-(
|
|
|
|
const auto &lastEffect = GetMenuManager(project).mLastEffect;
|
|
wxString buildMenuLabel;
|
|
if (!lastEffect.IsEmpty()) {
|
|
buildMenuLabel.Printf(_("Repeat %s"),
|
|
EffectManager::Get().GetCommandName(lastEffect));
|
|
}
|
|
else
|
|
buildMenuLabel = _("Repeat Last Effect");
|
|
|
|
return Menu( _("Effe&ct"),
|
|
#ifdef EXPERIMENTAL_EFFECT_MANAGEMENT
|
|
Command( wxT("ManageEffects"), XXO("Add / Remove Plug-ins..."),
|
|
FN(OnManageEffects), AudioIONotBusyFlag ),
|
|
|
|
Separator(),
|
|
|
|
#endif
|
|
Command( wxT("RepeatLastEffect"), buildMenuLabel, false, FN(OnRepeatLastEffect),
|
|
AudioIONotBusyFlag | TimeSelectedFlag | WaveTracksSelectedFlag | HasLastEffectFlag, wxT("Ctrl+R") ),
|
|
|
|
Separator(),
|
|
|
|
Items( PopulateEffectsMenu(
|
|
EffectTypeProcess,
|
|
AudioIONotBusyFlag | TimeSelectedFlag | WaveTracksSelectedFlag,
|
|
IsRealtimeNotActiveFlag ) )
|
|
);
|
|
}
|
|
|
|
MenuTable::BaseItemPtr AnalyzeMenu( AudacityProject & )
|
|
{
|
|
using namespace MenuTable;
|
|
// All of this is a bit hacky until we can get more things connected into
|
|
// the plugin manager...sorry! :-(
|
|
|
|
return Menu( _("&Analyze"),
|
|
#ifdef EXPERIMENTAL_EFFECT_MANAGEMENT
|
|
Command( wxT("ManageAnalyzers"), XXO("Add / Remove Plug-ins..."),
|
|
FN(OnManageAnalyzers), AudioIONotBusyFlag ),
|
|
|
|
Separator(),
|
|
|
|
#endif
|
|
|
|
Command( wxT("ContrastAnalyser"), XXO("Contrast..."), FN(OnContrast),
|
|
AudioIONotBusyFlag | WaveTracksSelectedFlag | TimeSelectedFlag, wxT("Ctrl+Shift+T") ),
|
|
Command( wxT("PlotSpectrum"), XXO("Plot Spectrum..."), FN(OnPlotSpectrum),
|
|
AudioIONotBusyFlag | WaveTracksSelectedFlag | TimeSelectedFlag ),
|
|
|
|
Items( PopulateEffectsMenu(
|
|
EffectTypeAnalyze,
|
|
AudioIONotBusyFlag | TimeSelectedFlag | WaveTracksSelectedFlag,
|
|
IsRealtimeNotActiveFlag ) )
|
|
);
|
|
}
|
|
|
|
MenuTable::BaseItemPtr ToolsMenu( AudacityProject & )
|
|
{
|
|
using namespace MenuTable;
|
|
using Options = CommandManager::Options;
|
|
|
|
return Menu( _("T&ools"),
|
|
|
|
#ifdef EXPERIMENTAL_EFFECT_MANAGEMENT
|
|
Command( wxT("ManageTools"), XXO("Add / Remove Plug-ins..."),
|
|
FN(OnManageTools), AudioIONotBusyFlag ),
|
|
|
|
//Separator(),
|
|
|
|
#endif
|
|
|
|
Command( wxT("ManageMacros"), XXO("&Macros..."),
|
|
FN(OnManageMacros), AudioIONotBusyFlag ),
|
|
|
|
Menu( _("&Apply Macro"),
|
|
// Palette has no access key to ensure first letter navigation of sub menu
|
|
Command( wxT("ApplyMacrosPalette"), XXO("Palette..."),
|
|
FN(OnApplyMacrosPalette), AudioIONotBusyFlag ),
|
|
|
|
Separator(),
|
|
|
|
Items( PopulateMacrosMenu( AudioIONotBusyFlag ) )
|
|
),
|
|
|
|
Separator(),
|
|
|
|
Command( wxT("FancyScreenshot"), XXO("&Screenshot..."),
|
|
FN(OnScreenshot), AudioIONotBusyFlag ),
|
|
|
|
// PRL: team consensus for 2.2.0 was, we let end users have this diagnostic,
|
|
// as they used to in 1.3.x
|
|
//#ifdef IS_ALPHA
|
|
// TODO: What should we do here? Make benchmark a plug-in?
|
|
// Easy enough to do. We'd call it mod-self-test.
|
|
Command( wxT("Benchmark"), XXO("&Run Benchmark..."),
|
|
FN(OnBenchmark), AudioIONotBusyFlag ),
|
|
//#endif
|
|
|
|
Separator(),
|
|
|
|
Items( PopulateEffectsMenu(
|
|
EffectTypeTool,
|
|
AudioIONotBusyFlag,
|
|
AudioIONotBusyFlag ) )
|
|
|
|
#ifdef IS_ALPHA
|
|
,
|
|
|
|
Separator(),
|
|
|
|
Command( wxT("SimulateRecordingErrors"),
|
|
XXO("Simulate Recording Errors"),
|
|
FN(OnSimulateRecordingErrors),
|
|
AudioIONotBusyFlag,
|
|
Options{}.CheckState( gAudioIO->mSimulateRecordingErrors ) ),
|
|
Command( wxT("DetectUpstreamDropouts"),
|
|
XXO("Detect Upstream Dropouts"),
|
|
FN(OnDetectUpstreamDropouts),
|
|
AudioIONotBusyFlag,
|
|
Options{}.CheckState( gAudioIO->mDetectUpstreamDropouts ) )
|
|
#endif
|
|
);
|
|
}
|
|
|
|
MenuTable::BaseItemPtr WindowMenu( AudacityProject & )
|
|
{
|
|
#ifdef __WXMAC__
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// poor imitation of the Mac Windows Menu
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
using namespace MenuTable;
|
|
return Menu( _("&Window"),
|
|
/* i18n-hint: Standard Macintosh Window menu item: Make (the current
|
|
* window) shrink to an icon on the dock */
|
|
Command( wxT("MacMinimize"), XXO("&Minimize"), FN(OnMacMinimize),
|
|
NotMinimizedFlag, wxT("Ctrl+M") ),
|
|
/* i18n-hint: Standard Macintosh Window menu item: Make (the current
|
|
* window) full sized */
|
|
Command( wxT("MacZoom"), XXO("&Zoom"),
|
|
FN(OnMacZoom), NotMinimizedFlag ),
|
|
|
|
Separator(),
|
|
|
|
/* i18n-hint: Standard Macintosh Window menu item: Make all project
|
|
* windows un-hidden */
|
|
Command( wxT("MacBringAllToFront"), XXO("&Bring All to Front"),
|
|
FN(OnMacBringAllToFront), AlwaysEnabledFlag )
|
|
);
|
|
#else
|
|
return {};
|
|
#endif
|
|
}
|
|
|
|
MenuTable::BaseItemPtr ExtraMenu( AudacityProject & )
|
|
{
|
|
using namespace MenuTable;
|
|
static const auto pred =
|
|
[]{ return gPrefs->ReadBool(wxT("/GUI/ShowExtraMenus"), false); };
|
|
static const auto factory =
|
|
[](AudacityProject &){ return extraItems; };
|
|
return ConditionalItems( pred, Menu( _("Ext&ra"), factory ) );
|
|
}
|
|
|
|
MenuTable::BaseItemPtr ExtraMixerMenu( AudacityProject & )
|
|
{
|
|
using namespace MenuTable;
|
|
return Menu( _("Mi&xer"),
|
|
Command( wxT("OutputGain"), XXO("Ad&just Playback Volume..."),
|
|
FN(OnOutputGain), AlwaysEnabledFlag ),
|
|
Command( wxT("OutputGainInc"), XXO("&Increase Playback Volume"),
|
|
FN(OnOutputGainInc), AlwaysEnabledFlag ),
|
|
Command( wxT("OutputGainDec"), XXO("&Decrease Playback Volume"),
|
|
FN(OnOutputGainDec), AlwaysEnabledFlag ),
|
|
Command( wxT("InputGain"), XXO("Adj&ust Recording Volume..."),
|
|
FN(OnInputGain), AlwaysEnabledFlag ),
|
|
Command( wxT("InputGainInc"), XXO("I&ncrease Recording Volume"),
|
|
FN(OnInputGainInc), AlwaysEnabledFlag ),
|
|
Command( wxT("InputGainDec"), XXO("D&ecrease Recording Volume"),
|
|
FN(OnInputGainDec), AlwaysEnabledFlag )
|
|
);
|
|
}
|
|
|
|
MenuTable::BaseItemPtr ExtraDeviceMenu( AudacityProject & )
|
|
{
|
|
using namespace MenuTable;
|
|
return Menu( _("De&vice"),
|
|
Command( wxT("InputDevice"), XXO("Change &Recording Device..."),
|
|
FN(OnInputDevice),
|
|
AudioIONotBusyFlag, wxT("Shift+I") ),
|
|
Command( wxT("OutputDevice"), XXO("Change &Playback Device..."),
|
|
FN(OnOutputDevice),
|
|
AudioIONotBusyFlag, wxT("Shift+O") ),
|
|
Command( wxT("AudioHost"), XXO("Change Audio &Host..."), FN(OnAudioHost),
|
|
AudioIONotBusyFlag, wxT("Shift+H") ),
|
|
Command( wxT("InputChannels"), XXO("Change Recording Cha&nnels..."),
|
|
FN(OnInputChannels),
|
|
AudioIONotBusyFlag, wxT("Shift+N") )
|
|
);
|
|
}
|
|
|
|
MenuTable::BaseItemPtr ExtraGlobalCommands( AudacityProject & )
|
|
{
|
|
// Ceci n'est pas un menu
|
|
using namespace MenuTable;
|
|
using Options = CommandManager::Options;
|
|
return Items(
|
|
Command( wxT("PrevWindow"), XXO("Move Backward Through Active Windows"),
|
|
FN(OnPrevWindow), AlwaysEnabledFlag,
|
|
Options{ wxT("Alt+Shift+F6") }.IsGlobal() ),
|
|
Command( wxT("NextWindow"), XXO("Move Forward Through Active Windows"),
|
|
FN(OnNextWindow), AlwaysEnabledFlag,
|
|
Options{ wxT("Alt+F6") }.IsGlobal() )
|
|
);
|
|
}
|
|
|
|
MenuTable::BaseItemPtr ExtraFocusMenu( AudacityProject & )
|
|
{
|
|
using namespace MenuTable;
|
|
constexpr auto FocusedTracksFlags = TracksExistFlag | TrackPanelHasFocus;
|
|
|
|
return Menu( _("F&ocus"),
|
|
Command( wxT("PrevFrame"),
|
|
XXO("Move &Backward from Toolbars to Tracks"), FN(OnPrevFrame),
|
|
AlwaysEnabledFlag, wxT("Ctrl+Shift+F6") ),
|
|
Command( wxT("NextFrame"),
|
|
XXO("Move F&orward from Toolbars to Tracks"), FN(OnNextFrame),
|
|
AlwaysEnabledFlag, wxT("Ctrl+F6") ),
|
|
Command( wxT("PrevTrack"), XXO("Move Focus to &Previous Track"),
|
|
FN(OnCursorUp), FocusedTracksFlags, wxT("Up") ),
|
|
Command( wxT("NextTrack"), XXO("Move Focus to &Next Track"),
|
|
FN(OnCursorDown), FocusedTracksFlags, wxT("Down") ),
|
|
Command( wxT("FirstTrack"), XXO("Move Focus to &First Track"),
|
|
FN(OnFirstTrack), FocusedTracksFlags, wxT("Ctrl+Home") ),
|
|
Command( wxT("LastTrack"), XXO("Move Focus to &Last Track"),
|
|
FN(OnLastTrack), FocusedTracksFlags, wxT("Ctrl+End") ),
|
|
Command( wxT("ShiftUp"), XXO("Move Focus to P&revious and Select"),
|
|
FN(OnShiftUp), FocusedTracksFlags, wxT("Shift+Up") ),
|
|
Command( wxT("ShiftDown"), XXO("Move Focus to N&ext and Select"),
|
|
FN(OnShiftDown), FocusedTracksFlags, wxT("Shift+Down") ),
|
|
Command( wxT("Toggle"), XXO("&Toggle Focused Track"), FN(OnToggle),
|
|
FocusedTracksFlags, wxT("Return") ),
|
|
Command( wxT("ToggleAlt"), XXO("Toggle Focuse&d Track"), FN(OnToggle),
|
|
FocusedTracksFlags, wxT("NUMPAD_ENTER") )
|
|
);
|
|
}
|
|
|
|
MenuTable::BaseItemPtr ExtraTrackMenu( AudacityProject & )
|
|
{
|
|
using namespace MenuTable;
|
|
|
|
return Menu( _("&Track"),
|
|
Command( wxT("TrackPan"), XXO("Change P&an on Focused Track..."),
|
|
FN(OnTrackPan),
|
|
TrackPanelHasFocus | TracksExistFlag, wxT("Shift+P") ),
|
|
Command( wxT("TrackPanLeft"), XXO("Pan &Left on Focused Track"),
|
|
FN(OnTrackPanLeft),
|
|
TrackPanelHasFocus | TracksExistFlag, wxT("Alt+Shift+Left") ),
|
|
Command( wxT("TrackPanRight"), XXO("Pan &Right on Focused Track"),
|
|
FN(OnTrackPanRight),
|
|
TrackPanelHasFocus | TracksExistFlag, wxT("Alt+Shift+Right") ),
|
|
Command( wxT("TrackGain"), XXO("Change Gai&n on Focused Track..."),
|
|
FN(OnTrackGain),
|
|
TrackPanelHasFocus | TracksExistFlag, wxT("Shift+G") ),
|
|
Command( wxT("TrackGainInc"), XXO("&Increase Gain on Focused Track"),
|
|
FN(OnTrackGainInc),
|
|
TrackPanelHasFocus | TracksExistFlag, wxT("Alt+Shift+Up") ),
|
|
Command( wxT("TrackGainDec"), XXO("&Decrease Gain on Focused Track"),
|
|
FN(OnTrackGainDec),
|
|
TrackPanelHasFocus | TracksExistFlag, wxT("Alt+Shift+Down") ),
|
|
Command( wxT("TrackMenu"), XXO("Op&en Menu on Focused Track..."),
|
|
FN(OnTrackMenu),
|
|
TracksExistFlag | TrackPanelHasFocus, wxT("Shift+M\tskipKeydown") ),
|
|
Command( wxT("TrackMute"), XXO("M&ute/Unmute Focused Track"),
|
|
FN(OnTrackMute),
|
|
TracksExistFlag | TrackPanelHasFocus, wxT("Shift+U") ),
|
|
Command( wxT("TrackSolo"), XXO("&Solo/Unsolo Focused Track"),
|
|
FN(OnTrackSolo),
|
|
TracksExistFlag | TrackPanelHasFocus, wxT("Shift+S") ),
|
|
Command( wxT("TrackClose"), XXO("&Close Focused Track"),
|
|
FN(OnTrackClose),
|
|
AudioIONotBusyFlag | TrackPanelHasFocus | TracksExistFlag,
|
|
wxT("Shift+C") ),
|
|
Command( wxT("TrackMoveUp"), XXO("Move Focused Track U&p"),
|
|
FN(OnTrackMoveUp),
|
|
AudioIONotBusyFlag | TrackPanelHasFocus | TracksExistFlag ),
|
|
Command( wxT("TrackMoveDown"), XXO("Move Focused Track Do&wn"),
|
|
FN(OnTrackMoveDown),
|
|
AudioIONotBusyFlag | TrackPanelHasFocus | TracksExistFlag ),
|
|
Command( wxT("TrackMoveTop"), XXO("Move Focused Track to T&op"),
|
|
FN(OnTrackMoveTop),
|
|
AudioIONotBusyFlag | TrackPanelHasFocus | TracksExistFlag ),
|
|
Command( wxT("TrackMoveBottom"), XXO("Move Focused Track to &Bottom"),
|
|
FN(OnTrackMoveBottom),
|
|
AudioIONotBusyFlag | TrackPanelHasFocus | TracksExistFlag )
|
|
);
|
|
}
|
|
|
|
MenuTable::BaseItemPtr ExtraScriptablesIMenu( AudacityProject & )
|
|
{
|
|
using namespace MenuTable;
|
|
|
|
// These are the more useful to VI user Scriptables.
|
|
// i18n-hint: Scriptables are commands normally used from Python, Perl etc.
|
|
return Menu( _("Script&ables I"),
|
|
// Note that the PLUGIN_SYMBOL must have a space between words,
|
|
// whereas the short-form used here must not.
|
|
// (So if you did write "CompareAudio" for the PLUGIN_SYMBOL name, then
|
|
// you would have to use "Compareaudio" here.)
|
|
Command( wxT("SelectTime"), XXO("Select Time..."), FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag ),
|
|
Command( wxT("SelectFrequencies"), XXO("Select Frequencies..."),
|
|
FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag ),
|
|
Command( wxT("SelectTracks"), XXO("Select Tracks..."),
|
|
FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag ),
|
|
Command( wxT("SetTrackStatus"), XXO("Set Track Status..."),
|
|
FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag ),
|
|
Command( wxT("SetTrackAudio"), XXO("Set Track Audio..."),
|
|
FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag ),
|
|
Command( wxT("SetTrackVisuals"), XXO("Set Track Visuals..."),
|
|
FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag ),
|
|
Command( wxT("GetPreference"), XXO("Get Preference..."),
|
|
FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag ),
|
|
Command( wxT("SetPreference"), XXO("Set Preference..."),
|
|
FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag ),
|
|
Command( wxT("SetClip"), XXO("Set Clip..."), FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag ),
|
|
Command( wxT("SetEnvelope"), XXO("Set Envelope..."),
|
|
FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag ),
|
|
Command( wxT("SetLabel"), XXO("Set Label..."), FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag ),
|
|
Command( wxT("SetProject"), XXO("Set Project..."), FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag )
|
|
);
|
|
}
|
|
|
|
MenuTable::BaseItemPtr ExtraScriptablesIIMenu( AudacityProject & )
|
|
{
|
|
using namespace MenuTable;
|
|
|
|
// Less useful to VI users.
|
|
return Menu( _("Scripta&bles II"),
|
|
Command( wxT("Select"), XXO("Select..."), FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag ),
|
|
Command( wxT("SetTrack"), XXO("Set Track..."), FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag ),
|
|
Command( wxT("GetInfo"), XXO("Get Info..."), FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag ),
|
|
Command( wxT("Message"), XXO("Message..."), FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag ),
|
|
Command( wxT("Help"), XXO("Help..."), FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag ),
|
|
Command( wxT("Import2"), XXO("Import..."), FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag ),
|
|
Command( wxT("Export2"), XXO("Export..."), FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag ),
|
|
Command( wxT("OpenProject2"), XXO("Open Project..."),
|
|
FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag ),
|
|
Command( wxT("SaveProject2"), XXO("Save Project..."),
|
|
FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag ),
|
|
Command( wxT("Drag"), XXO("Move Mouse..."), FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag ),
|
|
Command( wxT("CompareAudio"), XXO("Compare Audio..."),
|
|
FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag ),
|
|
// i18n-hint: Screenshot in the help menu has a much bigger dialog.
|
|
Command( wxT("Screenshot"), XXO("Screenshot (short format)..."),
|
|
FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag )
|
|
);
|
|
}
|
|
|
|
MenuTable::BaseItemPtr ExtraWindowItems( AudacityProject & )
|
|
{
|
|
#ifdef __WXMAC__
|
|
using namespace MenuTable;
|
|
|
|
return Items(
|
|
/* i18n-hint: Shrink all project windows to icons on the Macintosh
|
|
tooldock */
|
|
Command( wxT("MacMinimizeAll"), XXO("Minimize All Projects"),
|
|
FN(OnMacMinimizeAll),
|
|
AlwaysEnabledFlag, wxT("Ctrl+Alt+M") )
|
|
);
|
|
#else
|
|
return nullptr;
|
|
#endif
|
|
}
|
|
|
|
MenuTable::BaseItemPtr ExtraMiscItems( AudacityProject &project )
|
|
{
|
|
using namespace MenuTable;
|
|
using Options = CommandManager::Options;
|
|
|
|
constexpr auto key =
|
|
#ifdef __WXMAC__
|
|
wxT("Ctrl+/")
|
|
#else
|
|
wxT("F11")
|
|
#endif
|
|
;
|
|
|
|
// Not a menu.
|
|
return Items(
|
|
// Accel key is not bindable.
|
|
Command( wxT("FullScreenOnOff"), XXO("&Full Screen (on/off)"),
|
|
FN(OnFullScreen),
|
|
AlwaysEnabledFlag,
|
|
Options{ key }
|
|
.CheckState( project.wxTopLevelWindow::IsFullScreen() ) ),
|
|
|
|
ExtraWindowItems
|
|
);
|
|
}
|
|
|
|
MenuTable::BaseItemPtr HelpMenu( AudacityProject & )
|
|
{
|
|
#ifdef __WXMAC__
|
|
wxGetApp().s_macHelpMenuTitleName = _("&Help");
|
|
#endif
|
|
|
|
using namespace MenuTable;
|
|
|
|
return Menu( _("&Help"),
|
|
Command( wxT("QuickFix"), XXO("&Quick Fix..."), FN(OnQuickFix),
|
|
AlwaysEnabledFlag ),
|
|
// DA: Emphasise it is the Audacity Manual (No separate DA manual).
|
|
#ifdef EXPERIMENTAL_DA
|
|
// 'Getting Started' rather than 'Quick Help' for DarkAudacity.
|
|
// At the moment the video tutorials are aspirational (aka do not exist yet).
|
|
// Emphasise that manual is for Audacity, not DarkAudacity.
|
|
Command( wxT("QuickHelp"), XXO("&Getting Started"), FN(OnQuickHelp) ),
|
|
Command( wxT("Manual"), XXO("Audacity &Manual"), FN(OnManual) ),
|
|
#else
|
|
Command( wxT("QuickHelp"), XXO("&Quick Help..."), FN(OnQuickHelp),
|
|
AlwaysEnabledFlag ),
|
|
Command( wxT("Manual"), XXO("&Manual..."), FN(OnManual),
|
|
AlwaysEnabledFlag ),
|
|
#endif
|
|
|
|
Separator(),
|
|
|
|
Menu( _("&Diagnostics"),
|
|
Command( wxT("DeviceInfo"), XXO("Au&dio Device Info..."),
|
|
FN(OnAudioDeviceInfo),
|
|
AudioIONotBusyFlag ),
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
Command( wxT("MidiDeviceInfo"), XXO("&MIDI Device Info..."),
|
|
FN(OnMidiDeviceInfo),
|
|
AudioIONotBusyFlag ),
|
|
#endif
|
|
Command( wxT("Log"), XXO("Show &Log..."), FN(OnShowLog),
|
|
AlwaysEnabledFlag ),
|
|
#if defined(EXPERIMENTAL_CRASH_REPORT)
|
|
Command( wxT("CrashReport"), XXO("&Generate Support Data..."),
|
|
FN(OnCrashReport), AlwaysEnabledFlag ),
|
|
#endif
|
|
Command( wxT("CheckDeps"), XXO("Chec&k Dependencies..."),
|
|
FN(OnCheckDependencies),
|
|
AudioIONotBusyFlag )
|
|
),
|
|
|
|
#ifndef __WXMAC__
|
|
Separator(),
|
|
#endif
|
|
|
|
// DA: Does not fully support update checking.
|
|
#ifndef EXPERIMENTAL_DA
|
|
Command( wxT("Updates"), XXO("&Check for Updates..."),
|
|
FN(OnCheckForUpdates),
|
|
AlwaysEnabledFlag ),
|
|
#endif
|
|
Command( wxT("About"), XXO("&About Audacity..."), FN(OnAbout),
|
|
AlwaysEnabledFlag )
|
|
);
|
|
}
|
|
|
|
}
|
|
|
|
void MenuCreator::CreateMenusAndCommands(AudacityProject &project)
|
|
{
|
|
CommandManager *c = project.GetCommandManager();
|
|
|
|
// The list of defaults to exclude depends on
|
|
// preference wxT("/GUI/Shortcuts/FullDefaults"), which may have changed.
|
|
c->SetMaxList();
|
|
|
|
auto menubar = c->AddMenuBar(wxT("appmenu"));
|
|
wxASSERT(menubar);
|
|
|
|
VisitItem( project, menuTree.get() );
|
|
|
|
project.SetMenuBar(menubar.release());
|
|
|
|
mLastFlags = AlwaysEnabledFlag;
|
|
|
|
#if defined(__WXDEBUG__)
|
|
// c->CheckDups();
|
|
#endif
|
|
}
|
|
|
|
#undef XXO
|
|
|
|
|
|
|
|
MenuTable::BaseItemPtrs PopulateMacrosMenu( CommandFlag flags )
|
|
{
|
|
MenuTable::BaseItemPtrs result;
|
|
wxArrayString names = MacroCommands::GetNames();
|
|
int i;
|
|
|
|
for (i = 0; i < (int)names.GetCount(); i++) {
|
|
wxString MacroID = ApplyMacroDialog::MacroIdOfName( names[i] );
|
|
result.push_back( MenuTable::Command( MacroID,
|
|
names[i], false, FN(OnApplyMacroDirectly),
|
|
flags ) );
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/// The effects come from a plug in list
|
|
/// This code iterates through the list, adding effects into
|
|
/// the menu.
|
|
MenuTable::BaseItemPtrs PopulateEffectsMenu(
|
|
EffectType type,
|
|
CommandFlag batchflags,
|
|
CommandFlag realflags)
|
|
{
|
|
MenuTable::BaseItemPtrs result;
|
|
PluginManager & pm = PluginManager::Get();
|
|
|
|
std::vector<const PluginDescriptor*> defplugs;
|
|
std::vector<const PluginDescriptor*> optplugs;
|
|
|
|
const PluginDescriptor *plug = pm.GetFirstPluginForEffectType(type);
|
|
while (plug)
|
|
{
|
|
if ( !plug->IsEnabled() ){
|
|
;// don't add to menus!
|
|
}
|
|
else if (plug->IsEffectDefault()
|
|
#ifdef EXPERIMENTAL_DA
|
|
// Move Nyquist prompt into nyquist group.
|
|
&& (plug->GetSymbol() != IdentInterfaceSymbol("Nyquist Effects Prompt"))
|
|
&& (plug->GetSymbol() != IdentInterfaceSymbol("Nyquist Tools Prompt"))
|
|
&& (plug->GetSymbol() != IdentInterfaceSymbol("Nyquist Prompt"))
|
|
#endif
|
|
)
|
|
defplugs.push_back(plug);
|
|
else
|
|
optplugs.push_back(plug);
|
|
plug = pm.GetNextPluginForEffectType(type);
|
|
}
|
|
|
|
wxString groupby = gPrefs->Read(wxT("/Effects/GroupBy"), wxT("name"));
|
|
|
|
using Comparator = bool(*)(const PluginDescriptor*, const PluginDescriptor*);
|
|
Comparator comp1, comp2;
|
|
if (groupby == wxT("sortby:name"))
|
|
comp1 = comp2 = SortEffectsByName;
|
|
else if (groupby == wxT("sortby:publisher:name"))
|
|
comp1 = SortEffectsByName, comp2 = SortEffectsByPublisherAndName;
|
|
else if (groupby == wxT("sortby:type:name"))
|
|
comp1 = SortEffectsByName, comp2 = SortEffectsByTypeAndName;
|
|
else if (groupby == wxT("groupby:publisher"))
|
|
comp1 = comp2 = SortEffectsByPublisher;
|
|
else if (groupby == wxT("groupby:type"))
|
|
comp1 = comp2 = SortEffectsByType;
|
|
else // name
|
|
comp1 = comp2 = SortEffectsByName;
|
|
|
|
std::sort( defplugs.begin(), defplugs.end(), comp1 );
|
|
if ( comp1 != comp2 )
|
|
std::stable_sort( optplugs.begin(), optplugs.end(), comp2 );
|
|
|
|
AddEffectMenuItems( result, defplugs, batchflags, realflags, true );
|
|
|
|
if (defplugs.size() && optplugs.size())
|
|
result.push_back( MenuTable::Separator() );
|
|
|
|
AddEffectMenuItems( result, optplugs, batchflags, realflags, false );
|
|
|
|
return result;
|
|
}
|
|
|
|
void AddEffectMenuItems(
|
|
MenuTable::BaseItemPtrs &table,
|
|
std::vector<const PluginDescriptor*> & plugs,
|
|
CommandFlag batchflags,
|
|
CommandFlag realflags,
|
|
bool isDefault)
|
|
{
|
|
size_t pluginCnt = plugs.size();
|
|
|
|
wxString groupBy = gPrefs->Read(wxT("/Effects/GroupBy"), wxT("name"));
|
|
|
|
bool grouped = false;
|
|
if (groupBy.StartsWith(wxT("groupby")))
|
|
{
|
|
grouped = true;
|
|
}
|
|
|
|
std::vector<bool> vHasDialog;
|
|
wxArrayString groupNames;
|
|
PluginIDList groupPlugs;
|
|
std::vector<CommandFlag> groupFlags;
|
|
if (grouped)
|
|
{
|
|
wxString last;
|
|
wxString current;
|
|
|
|
for (size_t i = 0; i < pluginCnt; i++)
|
|
{
|
|
const PluginDescriptor *plug = plugs[i];
|
|
|
|
bool hasDialog = plug->GetSymbol().Msgid().Contains("...");
|
|
auto name = plug->GetSymbol().Translation();
|
|
|
|
if (plug->IsEffectInteractive())
|
|
{
|
|
name += wxT("...");
|
|
}
|
|
|
|
if (groupBy == wxT("groupby:publisher"))
|
|
{
|
|
current = EffectManager::Get().GetVendorName(plug->GetID());
|
|
if (current.IsEmpty())
|
|
{
|
|
current = _("Unknown");
|
|
}
|
|
}
|
|
else if (groupBy == wxT("groupby:type"))
|
|
{
|
|
current = EffectManager::Get().GetEffectFamilyName(plug->GetID());
|
|
if (current.IsEmpty())
|
|
{
|
|
current = _("Unknown");
|
|
}
|
|
}
|
|
|
|
if (current != last)
|
|
{
|
|
using namespace MenuTable;
|
|
BaseItemPtrs temp;
|
|
bool bInSubmenu = !last.IsEmpty() && (groupNames.Count() > 1);
|
|
|
|
AddEffectMenuItemGroup(temp,
|
|
groupNames, vHasDialog,
|
|
groupPlugs, groupFlags, isDefault);
|
|
|
|
table.push_back( MenuOrItems(
|
|
( bInSubmenu ? last : wxString{} ), std::move( temp )
|
|
) );
|
|
|
|
groupNames.Clear();
|
|
vHasDialog.clear();
|
|
groupPlugs.Clear();
|
|
groupFlags.clear();
|
|
last = current;
|
|
}
|
|
|
|
groupNames.Add(name);
|
|
vHasDialog.push_back(hasDialog);
|
|
groupPlugs.Add(plug->GetID());
|
|
groupFlags.push_back(plug->IsEffectRealtime() ? realflags : batchflags);
|
|
}
|
|
|
|
if (groupNames.GetCount() > 0)
|
|
{
|
|
using namespace MenuTable;
|
|
BaseItemPtrs temp;
|
|
bool bInSubmenu = groupNames.Count() > 1;
|
|
|
|
AddEffectMenuItemGroup(temp,
|
|
groupNames, vHasDialog, groupPlugs, groupFlags, isDefault);
|
|
|
|
table.push_back( MenuOrItems(
|
|
( bInSubmenu ? current : wxString{} ), std::move( temp )
|
|
) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (size_t i = 0; i < pluginCnt; i++)
|
|
{
|
|
const PluginDescriptor *plug = plugs[i];
|
|
|
|
bool hasDialog = plug->GetSymbol().Msgid().Contains("...");
|
|
auto name = plug->GetSymbol().Translation();
|
|
|
|
if (plug->IsEffectInteractive())
|
|
{
|
|
name += wxT("...");
|
|
}
|
|
|
|
wxString group = wxEmptyString;
|
|
if (groupBy == wxT("sortby:publisher:name"))
|
|
{
|
|
group = EffectManager::Get().GetVendorName(plug->GetID());
|
|
}
|
|
else if (groupBy == wxT("sortby:type:name"))
|
|
{
|
|
group = EffectManager::Get().GetEffectFamilyName(plug->GetID());
|
|
}
|
|
|
|
if (plug->IsEffectDefault())
|
|
{
|
|
group = wxEmptyString;
|
|
}
|
|
|
|
if (!group.IsEmpty())
|
|
{
|
|
group += wxT(": ");
|
|
}
|
|
|
|
groupNames.Add(group + name);
|
|
vHasDialog.push_back(hasDialog);
|
|
groupPlugs.Add(plug->GetID());
|
|
groupFlags.push_back(plug->IsEffectRealtime() ? realflags : batchflags);
|
|
}
|
|
|
|
if (groupNames.GetCount() > 0)
|
|
{
|
|
AddEffectMenuItemGroup(table, groupNames, vHasDialog, groupPlugs, groupFlags, isDefault);
|
|
}
|
|
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void AddEffectMenuItemGroup(
|
|
MenuTable::BaseItemPtrs &table,
|
|
const wxArrayString & names,
|
|
const std::vector<bool> &vHasDialog,
|
|
const PluginIDList & plugs,
|
|
const std::vector<CommandFlag> & flags,
|
|
bool isDefault)
|
|
{
|
|
const int namesCnt = (int) names.GetCount();
|
|
int perGroup;
|
|
|
|
#if defined(__WXGTK__)
|
|
gPrefs->Read(wxT("/Effects/MaxPerGroup"), &perGroup, 15);
|
|
#else
|
|
gPrefs->Read(wxT("/Effects/MaxPerGroup"), &perGroup, 0);
|
|
#endif
|
|
|
|
int groupCnt = namesCnt;
|
|
for (int i = 0; i < namesCnt; i++)
|
|
{
|
|
while (i + 1 < namesCnt && names[i].IsSameAs(names[i + 1]))
|
|
{
|
|
i++;
|
|
groupCnt--;
|
|
}
|
|
}
|
|
|
|
// The "default" effects shouldn't be broken into subgroups
|
|
if (namesCnt > 0 && isDefault)
|
|
{
|
|
perGroup = 0;
|
|
}
|
|
|
|
int max = perGroup;
|
|
int items = perGroup;
|
|
|
|
if (max > groupCnt)
|
|
{
|
|
max = 0;
|
|
}
|
|
|
|
using namespace MenuTable;
|
|
auto pTable = &table;
|
|
BaseItemPtrs temp1;
|
|
|
|
int groupNdx = 0;
|
|
for (int i = 0; i < namesCnt; i++)
|
|
{
|
|
if (max > 0 && items == max)
|
|
{
|
|
// start collecting items for the next submenu
|
|
pTable = &temp1;
|
|
}
|
|
|
|
if (i + 1 < namesCnt && names[i].IsSameAs(names[i + 1]))
|
|
{
|
|
// collect a sub-menu for like-named items
|
|
const wxString name = names[i];
|
|
BaseItemPtrs temp2;
|
|
while (i < namesCnt && names[i].IsSameAs(name))
|
|
{
|
|
const PluginDescriptor *plug = PluginManager::Get().GetPlugin(plugs[i]);
|
|
wxString item = plug->GetPath();
|
|
if( plug->GetPluginType() == PluginTypeEffect )
|
|
temp2.push_back( Command( item,
|
|
item,
|
|
item.Contains("..."),
|
|
FN(OnEffect),
|
|
flags[i],
|
|
CommandManager::Options{}
|
|
.IsEffect().Parameter( plugs[i] ) ) );
|
|
|
|
i++;
|
|
}
|
|
pTable->push_back( Menu( name, std::move( temp2 ) ) );
|
|
i--;
|
|
}
|
|
else
|
|
{
|
|
// collect one item
|
|
const PluginDescriptor *plug = PluginManager::Get().GetPlugin(plugs[i]);
|
|
if( plug->GetPluginType() == PluginTypeEffect )
|
|
pTable->push_back( Command( names[i],
|
|
names[i],
|
|
vHasDialog[i],
|
|
FN(OnEffect),
|
|
flags[i],
|
|
CommandManager::Options{}
|
|
.IsEffect().Parameter( plugs[i] ) ) );
|
|
}
|
|
|
|
if (max > 0)
|
|
{
|
|
items--;
|
|
if (items == 0 || i + 1 == namesCnt)
|
|
{
|
|
int end = groupNdx + max;
|
|
if (end + 1 > groupCnt)
|
|
{
|
|
end = groupCnt;
|
|
}
|
|
// Done collecting
|
|
table.push_back( Menu(
|
|
wxString::Format(_("Plug-in %d to %d"), groupNdx + 1, end),
|
|
std::move( temp1 )
|
|
) );
|
|
items = max;
|
|
pTable = &table;
|
|
}
|
|
groupNdx++;
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
#undef FN
|
|
|
|
// TODO: This surely belongs in CommandManager?
|
|
void MenuManager::ModifyUndoMenuItems(AudacityProject &project)
|
|
{
|
|
wxString desc;
|
|
auto &undoManager = *project.GetUndoManager();
|
|
auto &commandManager = *project.GetCommandManager();
|
|
int cur = undoManager.GetCurrentState();
|
|
|
|
if (undoManager.UndoAvailable()) {
|
|
undoManager.GetShortDescription(cur, &desc);
|
|
commandManager.Modify(wxT("Undo"),
|
|
wxString::Format(_("&Undo %s"),
|
|
desc));
|
|
commandManager.Enable(wxT("Undo"), project.UndoAvailable());
|
|
}
|
|
else {
|
|
commandManager.Modify(wxT("Undo"),
|
|
_("&Undo"));
|
|
}
|
|
|
|
if (undoManager.RedoAvailable()) {
|
|
undoManager.GetShortDescription(cur+1, &desc);
|
|
commandManager.Modify(wxT("Redo"),
|
|
wxString::Format(_("&Redo %s"),
|
|
desc));
|
|
commandManager.Enable(wxT("Redo"), project.RedoAvailable());
|
|
}
|
|
else {
|
|
commandManager.Modify(wxT("Redo"),
|
|
_("&Redo"));
|
|
commandManager.Enable(wxT("Redo"), false);
|
|
}
|
|
}
|
|
|
|
void MenuCreator::RebuildMenuBar(AudacityProject &project)
|
|
{
|
|
// On OSX, we can't rebuild the menus while a modal dialog is being shown
|
|
// since the enabled state for menus like Quit and Preference gets out of
|
|
// sync with wxWidgets idea of what it should be.
|
|
#if defined(__WXMAC__) && defined(__WXDEBUG__)
|
|
{
|
|
wxDialog *dlg =
|
|
wxDynamicCast(wxGetTopLevelParent(wxWindow::FindFocus()), wxDialog);
|
|
wxASSERT((!dlg || !dlg->IsModal()));
|
|
}
|
|
#endif
|
|
|
|
// Delete the menus, since we will soon recreate them.
|
|
// Rather oddly, the menus don't vanish as a result of doing this.
|
|
{
|
|
std::unique_ptr<wxMenuBar> menuBar{ project.GetMenuBar() };
|
|
project.DetachMenuBar();
|
|
// menuBar gets deleted here
|
|
}
|
|
|
|
project.GetCommandManager()->PurgeData();
|
|
|
|
CreateMenusAndCommands(project);
|
|
|
|
ModuleManager::Get().Dispatch(MenusRebuilt);
|
|
}
|
|
|
|
void AudacityProject::RebuildOtherMenus()
|
|
{
|
|
}
|
|
|
|
CommandFlag MenuManager::GetFocusedFrame(AudacityProject &project)
|
|
{
|
|
wxWindow *w = wxWindow::FindFocus();
|
|
|
|
while (w && project.GetToolManager() && project.GetTrackPanel()) {
|
|
if (w == project.GetToolManager()->GetTopDock()) {
|
|
return TopDockHasFocus;
|
|
}
|
|
|
|
if (w == project.GetRulerPanel())
|
|
return RulerHasFocus;
|
|
|
|
if (w == project.GetTrackPanel()) {
|
|
return TrackPanelHasFocus;
|
|
}
|
|
// LIE if Lyrics window has focus.
|
|
// we want to act as if TrackPanel has focus.
|
|
if (w == project.GetLyricsWindow()) {
|
|
return TrackPanelHasFocus;
|
|
}
|
|
if (w == project.GetToolManager()->GetBotDock()) {
|
|
return BotDockHasFocus;
|
|
}
|
|
|
|
w = w->GetParent();
|
|
}
|
|
|
|
return AlwaysEnabledFlag;
|
|
}
|
|
|
|
CommandFlag MenuManager::GetUpdateFlags
|
|
(AudacityProject &project, bool checkActive)
|
|
{
|
|
// This method determines all of the flags that determine whether
|
|
// certain menu items and commands should be enabled or disabled,
|
|
// and returns them in a bitfield. Note that if none of the flags
|
|
// have changed, it's not necessary to even check for updates.
|
|
auto flags = AlwaysEnabledFlag;
|
|
// static variable, used to remember flags for next time.
|
|
static auto lastFlags = flags;
|
|
|
|
if (auto focus = wxWindow::FindFocus()) {
|
|
while (focus && focus->GetParent())
|
|
focus = focus->GetParent();
|
|
if (focus && !static_cast<wxTopLevelWindow*>(focus)->IsIconized())
|
|
flags |= NotMinimizedFlag;
|
|
}
|
|
|
|
// quick 'short-circuit' return.
|
|
if ( checkActive && !project.IsActive() ){
|
|
// short cirucit return should preserve flags that have not been calculated.
|
|
flags = (lastFlags & ~NotMinimizedFlag) | flags;
|
|
lastFlags = flags;
|
|
return flags;
|
|
}
|
|
|
|
if (!gAudioIO->IsAudioTokenActive(project.GetAudioIOToken()))
|
|
flags |= AudioIONotBusyFlag;
|
|
else
|
|
flags |= AudioIOBusyFlag;
|
|
|
|
if( gAudioIO->IsPaused() )
|
|
flags |= PausedFlag;
|
|
else
|
|
flags |= NotPausedFlag;
|
|
|
|
auto &viewInfo = project.GetViewInfo();
|
|
const auto &selectedRegion = viewInfo.selectedRegion;
|
|
|
|
if (!selectedRegion.isPoint())
|
|
flags |= TimeSelectedFlag;
|
|
|
|
auto tracks = project.GetTracks();
|
|
auto trackRange = tracks->Any();
|
|
if ( trackRange )
|
|
flags |= TracksExistFlag;
|
|
trackRange.Visit(
|
|
[&](LabelTrack *lt) {
|
|
flags |= LabelTracksExistFlag;
|
|
|
|
if (lt->GetSelected()) {
|
|
flags |= TracksSelectedFlag;
|
|
for (int i = 0; i < lt->GetNumLabels(); i++) {
|
|
const LabelStruct *ls = lt->GetLabel(i);
|
|
if (ls->getT0() >= selectedRegion.t0() &&
|
|
ls->getT1() <= selectedRegion.t1()) {
|
|
flags |= LabelsSelectedFlag;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (lt->IsTextSelected()) {
|
|
flags |= CutCopyAvailableFlag;
|
|
}
|
|
},
|
|
[&](WaveTrack *t) {
|
|
flags |= WaveTracksExistFlag;
|
|
flags |= PlayableTracksExistFlag;
|
|
if (t->GetSelected()) {
|
|
flags |= TracksSelectedFlag;
|
|
// TODO: more-than-two-channels
|
|
if (TrackList::Channels(t).size() > 1) {
|
|
flags |= StereoRequiredFlag;
|
|
}
|
|
flags |= WaveTracksSelectedFlag;
|
|
flags |= AudioTracksSelectedFlag;
|
|
}
|
|
if( t->GetEndTime() > t->GetStartTime() )
|
|
flags |= HasWaveDataFlag;
|
|
}
|
|
#if defined(USE_MIDI)
|
|
,
|
|
[&](NoteTrack *nt) {
|
|
flags |= NoteTracksExistFlag;
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
flags |= PlayableTracksExistFlag;
|
|
#endif
|
|
|
|
if (nt->GetSelected()) {
|
|
flags |= TracksSelectedFlag;
|
|
flags |= NoteTracksSelectedFlag;
|
|
flags |= AudioTracksSelectedFlag; // even if not EXPERIMENTAL_MIDI_OUT
|
|
}
|
|
}
|
|
#endif
|
|
);
|
|
|
|
if((AudacityProject::msClipT1 - AudacityProject::msClipT0) > 0.0)
|
|
flags |= ClipboardFlag;
|
|
|
|
auto &undoManager = *project.GetUndoManager();
|
|
|
|
if (undoManager.UnsavedChanges() || !project.IsProjectSaved())
|
|
flags |= UnsavedChangesFlag;
|
|
|
|
if (!mLastEffect.IsEmpty())
|
|
flags |= HasLastEffectFlag;
|
|
|
|
if (project.UndoAvailable())
|
|
flags |= UndoAvailableFlag;
|
|
|
|
if (project.RedoAvailable())
|
|
flags |= RedoAvailableFlag;
|
|
|
|
if (project.ZoomInAvailable() && (flags & TracksExistFlag))
|
|
flags |= ZoomInAvailableFlag;
|
|
|
|
if (project.ZoomOutAvailable() && (flags & TracksExistFlag))
|
|
flags |= ZoomOutAvailableFlag;
|
|
|
|
// TextClipFlag is currently unused (Jan 2017, 2.1.3 alpha)
|
|
// and LabelTrack::IsTextClipSupported() is quite slow on Linux,
|
|
// so disable for now (See bug 1575).
|
|
// if ((flags & LabelTracksExistFlag) && LabelTrack::IsTextClipSupported())
|
|
// flags |= TextClipFlag;
|
|
|
|
flags |= GetFocusedFrame(project);
|
|
|
|
double start, end;
|
|
project.GetPlayRegion(&start, &end);
|
|
if (project.IsPlayRegionLocked())
|
|
flags |= PlayRegionLockedFlag;
|
|
else if (start != end)
|
|
flags |= PlayRegionNotLockedFlag;
|
|
|
|
if (flags & AudioIONotBusyFlag) {
|
|
if (flags & TimeSelectedFlag) {
|
|
if (flags & TracksSelectedFlag) {
|
|
flags |= CutCopyAvailableFlag;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (wxGetApp().GetRecentFiles()->GetCount() > 0)
|
|
flags |= HaveRecentFiles;
|
|
|
|
if (project.IsSyncLocked())
|
|
flags |= IsSyncLockedFlag;
|
|
else
|
|
flags |= IsNotSyncLockedFlag;
|
|
|
|
if (!EffectManager::Get().RealtimeIsActive())
|
|
flags |= IsRealtimeNotActiveFlag;
|
|
|
|
if (!project.IsCapturing())
|
|
flags |= CaptureNotBusyFlag;
|
|
|
|
ControlToolBar *bar = project.GetControlToolBar();
|
|
if (bar->ControlToolBar::CanStopAudioStream())
|
|
flags |= CanStopAudioStreamFlag;
|
|
|
|
lastFlags = flags;
|
|
return flags;
|
|
}
|
|
|
|
// Select the full time range, if no
|
|
// time range is selected.
|
|
void AudacityProject::SelectAllIfNone()
|
|
{
|
|
auto flags = GetMenuManager(*this).GetUpdateFlags(*this);
|
|
if(!(flags & TracksSelectedFlag) ||
|
|
(mViewInfo.selectedRegion.isPoint()))
|
|
SelectActions::DoSelectSomething(*this);
|
|
}
|
|
|
|
// Stop playing or recording, if paused.
|
|
void AudacityProject::StopIfPaused()
|
|
{
|
|
auto flags = GetMenuManager(*this).GetUpdateFlags(*this);
|
|
if( flags & PausedFlag )
|
|
TransportActions::DoStop(*this);
|
|
}
|
|
|
|
void MenuManager::ModifyAllProjectToolbarMenus()
|
|
{
|
|
AProjectArray::iterator i;
|
|
for (i = gAudacityProjects.begin(); i != gAudacityProjects.end(); ++i) {
|
|
auto &project = **i;
|
|
GetMenuManager(project).ModifyToolbarMenus(project);
|
|
}
|
|
}
|
|
|
|
void MenuManager::ModifyToolbarMenus(AudacityProject &project)
|
|
{
|
|
// Refreshes can occur during shutdown and the toolmanager may already
|
|
// be deleted, so protect against it.
|
|
auto toolManager = project.GetToolManager();
|
|
if (!toolManager) {
|
|
return;
|
|
}
|
|
|
|
auto &commandManager = *project.GetCommandManager();
|
|
|
|
commandManager.Check(wxT("ShowScrubbingTB"),
|
|
toolManager->IsVisible(ScrubbingBarID));
|
|
commandManager.Check(wxT("ShowDeviceTB"),
|
|
toolManager->IsVisible(DeviceBarID));
|
|
commandManager.Check(wxT("ShowEditTB"),
|
|
toolManager->IsVisible(EditBarID));
|
|
commandManager.Check(wxT("ShowMeterTB"),
|
|
toolManager->IsVisible(MeterBarID));
|
|
commandManager.Check(wxT("ShowRecordMeterTB"),
|
|
toolManager->IsVisible(RecordMeterBarID));
|
|
commandManager.Check(wxT("ShowPlayMeterTB"),
|
|
toolManager->IsVisible(PlayMeterBarID));
|
|
commandManager.Check(wxT("ShowMixerTB"),
|
|
toolManager->IsVisible(MixerBarID));
|
|
commandManager.Check(wxT("ShowSelectionTB"),
|
|
toolManager->IsVisible(SelectionBarID));
|
|
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
|
|
commandManager.Check(wxT("ShowSpectralSelectionTB"),
|
|
toolManager->IsVisible(SpectralSelectionBarID));
|
|
#endif
|
|
commandManager.Check(wxT("ShowToolsTB"),
|
|
toolManager->IsVisible(ToolsBarID));
|
|
commandManager.Check(wxT("ShowTranscriptionTB"),
|
|
toolManager->IsVisible(TranscriptionBarID));
|
|
commandManager.Check(wxT("ShowTransportTB"),
|
|
toolManager->IsVisible(TransportBarID));
|
|
|
|
// Now, go through each toolbar, and call EnableDisableButtons()
|
|
for (int i = 0; i < ToolBarCount; i++) {
|
|
toolManager->GetToolBar(i)->EnableDisableButtons();
|
|
}
|
|
|
|
// These don't really belong here, but it's easier and especially so for
|
|
// the Edit toolbar and the sync-lock menu item.
|
|
bool active;
|
|
gPrefs->Read(wxT("/AudioIO/SoundActivatedRecord"),&active, false);
|
|
commandManager.Check(wxT("SoundActivation"), active);
|
|
#ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT
|
|
gPrefs->Read(wxT("/AudioIO/AutomatedInputLevelAdjustment"),&active, false);
|
|
commandManager.Check(wxT("AutomatedInputLevelAdjustmentOnOff"), active);
|
|
#endif
|
|
|
|
active = TracksPrefs::GetPinnedHeadPreference();
|
|
commandManager.Check(wxT("PinnedHead"), active);
|
|
|
|
#ifdef EXPERIMENTAL_DA
|
|
gPrefs->Read(wxT("/AudioIO/Duplex"),&active, false);
|
|
#else
|
|
gPrefs->Read(wxT("/AudioIO/Duplex"),&active, true);
|
|
#endif
|
|
commandManager.Check(wxT("Overdub"), active);
|
|
gPrefs->Read(wxT("/AudioIO/SWPlaythrough"),&active, false);
|
|
commandManager.Check(wxT("SWPlaythrough"), active);
|
|
gPrefs->Read(wxT("/GUI/SyncLockTracks"), &active, false);
|
|
project.SetSyncLock(active);
|
|
commandManager.Check(wxT("SyncLock"), active);
|
|
gPrefs->Read(wxT("/GUI/TypeToCreateLabel"),&active, true);
|
|
commandManager.Check(wxT("TypeToCreateLabel"), active);
|
|
}
|
|
|
|
// checkActive is a temporary hack that should be removed as soon as we
|
|
// get multiple effect preview working
|
|
void MenuManager::UpdateMenus(AudacityProject &project, bool checkActive)
|
|
{
|
|
//ANSWER-ME: Why UpdateMenus only does active project?
|
|
//JKC: Is this test fixing a bug when multiple projects are open?
|
|
//so that menu states work even when different in different projects?
|
|
if (&project != GetActiveProject())
|
|
return;
|
|
|
|
auto flags = GetMenuManager(project).GetUpdateFlags(project, checkActive);
|
|
auto flags2 = flags;
|
|
|
|
// We can enable some extra items if we have select-all-on-none.
|
|
//EXPLAIN-ME: Why is this here rather than in GetUpdateFlags()?
|
|
//ANSWER: Because flags2 is used in the menu enable/disable.
|
|
//The effect still needs flags to determine whether it will need
|
|
//to actually do the 'select all' to make the command valid.
|
|
if (mWhatIfNoSelection != 0)
|
|
{
|
|
if ((flags & TracksExistFlag))
|
|
{
|
|
flags2 |= TracksSelectedFlag;
|
|
if ((flags & WaveTracksExistFlag))
|
|
{
|
|
flags2 |= TimeSelectedFlag
|
|
| WaveTracksSelectedFlag
|
|
| CutCopyAvailableFlag;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( mStopIfWasPaused )
|
|
{
|
|
if( flags & PausedFlag ){
|
|
flags2 |= AudioIONotBusyFlag;
|
|
}
|
|
}
|
|
|
|
// Return from this function if nothing's changed since
|
|
// the last time we were here.
|
|
if (flags == mLastFlags)
|
|
return;
|
|
mLastFlags = flags;
|
|
|
|
auto &commandManager = *project.GetCommandManager();
|
|
|
|
commandManager.EnableUsingFlags(flags2 , NoFlagsSpecified);
|
|
|
|
// With select-all-on-none, some items that we don't want enabled may have
|
|
// been enabled, since we changed the flags. Here we manually disable them.
|
|
// 0 is grey out, 1 is Autoselect, 2 is Give warnings.
|
|
if (mWhatIfNoSelection != 0)
|
|
{
|
|
if (!(flags & TimeSelectedFlag) | !(flags & TracksSelectedFlag))
|
|
{
|
|
commandManager.Enable(wxT("SplitCut"), false);
|
|
commandManager.Enable(wxT("SplitDelete"), false);
|
|
}
|
|
if (!(flags & WaveTracksSelectedFlag))
|
|
{
|
|
commandManager.Enable(wxT("Split"), false);
|
|
}
|
|
if (!(flags & TimeSelectedFlag) | !(flags & WaveTracksSelectedFlag))
|
|
{
|
|
commandManager.Enable(wxT("ExportSel"), false);
|
|
commandManager.Enable(wxT("SplitNew"), false);
|
|
}
|
|
if (!(flags & TimeSelectedFlag) | !(flags & AudioTracksSelectedFlag))
|
|
{
|
|
commandManager.Enable(wxT("Trim"), false);
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
if (flags & CutCopyAvailableFlag) {
|
|
GetCommandManager()->Enable(wxT("Copy"), true);
|
|
GetCommandManager()->Enable(wxT("Cut"), true);
|
|
}
|
|
#endif
|
|
|
|
MenuManager::ModifyToolbarMenus(project);
|
|
}
|
|
|
|
//sort based on flags. see Project.h for sort flags
|
|
void AudacityProject::SortTracks(int flags)
|
|
{
|
|
auto GetTime = [](const Track *t) {
|
|
return t->TypeSwitch< double >(
|
|
[&](const WaveTrack* w) {
|
|
auto stime = w->GetEndTime();
|
|
|
|
int ndx;
|
|
for (ndx = 0; ndx < w->GetNumClips(); ndx++) {
|
|
const auto c = w->GetClipByIndex(ndx);
|
|
if (c->GetNumSamples() == 0)
|
|
continue;
|
|
stime = std::min(stime, c->GetStartTime());
|
|
}
|
|
return stime;
|
|
},
|
|
[&](const LabelTrack* l) {
|
|
return l->GetStartTime();
|
|
}
|
|
);
|
|
};
|
|
|
|
size_t ndx = 0;
|
|
// This one place outside of TrackList where we must use undisguised
|
|
// std::list iterators! Avoid this elsewhere!
|
|
std::vector<TrackNodePointer> arr;
|
|
arr.reserve(mTracks->size());
|
|
|
|
// First find the permutation.
|
|
// This routine, very unusually, deals with the underlying stl list
|
|
// iterators, not with TrackIter! Dangerous!
|
|
for (auto iter = mTracks->ListOfTracks::begin(),
|
|
end = mTracks->ListOfTracks::end(); iter != end; ++iter) {
|
|
const auto &track = *iter;
|
|
if ( !track->IsLeader() )
|
|
// keep channels contiguous
|
|
ndx++;
|
|
else {
|
|
auto size = arr.size();
|
|
for (ndx = 0; ndx < size;) {
|
|
Track &arrTrack = **arr[ndx].first;
|
|
auto channels = TrackList::Channels(&arrTrack);
|
|
if(flags & kAudacitySortByName) {
|
|
//do case insensitive sort - cmpNoCase returns less than zero if the string is 'less than' its argument
|
|
//also if we have case insensitive equality, then we need to sort by case as well
|
|
//We sort 'b' before 'B' accordingly. We uncharacteristically use greater than for the case sensitive
|
|
//compare because 'b' is greater than 'B' in ascii.
|
|
auto cmpValue = track->GetName().CmpNoCase(arrTrack.GetName());
|
|
if ( cmpValue < 0 ||
|
|
( 0 == cmpValue &&
|
|
track->GetName().CompareTo(arrTrack.GetName()) > 0 ) )
|
|
break;
|
|
}
|
|
//sort by time otherwise
|
|
else if(flags & kAudacitySortByTime) {
|
|
auto time1 = TrackList::Channels(track.get()).min( GetTime );
|
|
|
|
//get candidate's (from sorted array) time
|
|
auto time2 = channels.min( GetTime );
|
|
|
|
if (time1 < time2)
|
|
break;
|
|
}
|
|
ndx += channels.size();
|
|
}
|
|
}
|
|
arr.insert(arr.begin() + ndx, TrackNodePointer{iter, mTracks.get()});
|
|
}
|
|
|
|
// Now apply the permutation
|
|
mTracks->Permute(arr);
|
|
}
|
|
|
|
void MenuCommandHandler::OnSortTime(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
project.SortTracks(kAudacitySortByTime);
|
|
|
|
project.PushState(_("Tracks sorted by time"), _("Sort by Time"));
|
|
|
|
auto trackPanel = project.GetTrackPanel();
|
|
trackPanel->Refresh(false);
|
|
}
|
|
|
|
void MenuCommandHandler::OnSortName(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
project.SortTracks(kAudacitySortByName);
|
|
|
|
project.PushState(_("Tracks sorted by name"), _("Sort by Name"));
|
|
|
|
auto trackPanel = project.GetTrackPanel();
|
|
trackPanel->Refresh(false);
|
|
}
|
|
|
|
/// The following method moves to the previous track
|
|
/// selecting and unselecting depending if you are on the start of a
|
|
/// block or not.
|
|
|
|
/// \todo Merge related methods, OnPrevTrack and OnNextTrack.
|
|
void MenuCommandHandler::DoPrevTrack( AudacityProject &project, bool shift )
|
|
{
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto tracks = project.GetTracks();
|
|
auto &selectionState = project.GetSelectionState();
|
|
auto mixerBoard = project.GetMixerBoard();
|
|
|
|
Track* t = trackPanel->GetFocusedTrack();
|
|
if( t == NULL ) // if there isn't one, focus on last
|
|
{
|
|
t = *tracks->Any().rbegin();
|
|
trackPanel->SetFocusedTrack( t );
|
|
trackPanel->EnsureVisible( t );
|
|
project.ModifyState(false);
|
|
return;
|
|
}
|
|
|
|
Track* p = NULL;
|
|
bool tSelected = false;
|
|
bool pSelected = false;
|
|
if( shift )
|
|
{
|
|
p = * -- tracks->FindLeader( t ); // Get previous track
|
|
if( p == NULL ) // On first track
|
|
{
|
|
// JKC: wxBell() is probably for accessibility, so a blind
|
|
// user knows they were at the top track.
|
|
wxBell();
|
|
if( mCircularTrackNavigation )
|
|
p = *tracks->Any().rbegin();
|
|
else
|
|
{
|
|
trackPanel->EnsureVisible( t );
|
|
return;
|
|
}
|
|
}
|
|
tSelected = t->GetSelected();
|
|
if (p)
|
|
pSelected = p->GetSelected();
|
|
if( tSelected && pSelected )
|
|
{
|
|
selectionState.SelectTrack
|
|
( *t, false, false, mixerBoard );
|
|
trackPanel->SetFocusedTrack( p ); // move focus to next track up
|
|
trackPanel->EnsureVisible( p );
|
|
project.ModifyState(false);
|
|
return;
|
|
}
|
|
if( tSelected && !pSelected )
|
|
{
|
|
selectionState.SelectTrack
|
|
( *p, true, false, mixerBoard );
|
|
trackPanel->SetFocusedTrack( p ); // move focus to next track up
|
|
trackPanel->EnsureVisible( p );
|
|
project.ModifyState(false);
|
|
return;
|
|
}
|
|
if( !tSelected && pSelected )
|
|
{
|
|
selectionState.SelectTrack
|
|
( *p, false, false, mixerBoard );
|
|
trackPanel->SetFocusedTrack( p ); // move focus to next track up
|
|
trackPanel->EnsureVisible( p );
|
|
project.ModifyState(false);
|
|
return;
|
|
}
|
|
if( !tSelected && !pSelected )
|
|
{
|
|
selectionState.SelectTrack
|
|
( *t, true, false, mixerBoard );
|
|
trackPanel->SetFocusedTrack( p ); // move focus to next track up
|
|
trackPanel->EnsureVisible( p );
|
|
project.ModifyState(false);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
p = * -- tracks->FindLeader( t ); // Get previous track
|
|
if( p == NULL ) // On first track so stay there?
|
|
{
|
|
wxBell();
|
|
if( mCircularTrackNavigation )
|
|
{
|
|
auto range = tracks->Leaders();
|
|
p = * range.rbegin(); // null if range is empty
|
|
trackPanel->SetFocusedTrack( p ); // Wrap to the last track
|
|
trackPanel->EnsureVisible( p );
|
|
project.ModifyState(false);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
trackPanel->EnsureVisible( t );
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
trackPanel->SetFocusedTrack( p ); // move focus to next track up
|
|
trackPanel->EnsureVisible( p );
|
|
project.ModifyState(false);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The following method moves to the next track,
|
|
/// selecting and unselecting depending if you are on the start of a
|
|
/// block or not.
|
|
void MenuCommandHandler::DoNextTrack( AudacityProject &project, bool shift )
|
|
{
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto tracks = project.GetTracks();
|
|
auto &selectionState = project.GetSelectionState();
|
|
auto mixerBoard = project.GetMixerBoard();
|
|
|
|
auto t = trackPanel->GetFocusedTrack(); // Get currently focused track
|
|
if( t == NULL ) // if there isn't one, focus on first
|
|
{
|
|
t = *tracks->Any().begin();
|
|
trackPanel->SetFocusedTrack( t );
|
|
trackPanel->EnsureVisible( t );
|
|
project.ModifyState(false);
|
|
return;
|
|
}
|
|
|
|
if( shift )
|
|
{
|
|
auto n = * ++ tracks->FindLeader( t ); // Get next track
|
|
if( n == NULL ) // On last track so stay there
|
|
{
|
|
wxBell();
|
|
if( mCircularTrackNavigation )
|
|
n = *tracks->Any().begin();
|
|
else
|
|
{
|
|
trackPanel->EnsureVisible( t );
|
|
return;
|
|
}
|
|
}
|
|
auto tSelected = t->GetSelected();
|
|
auto nSelected = n->GetSelected();
|
|
if( tSelected && nSelected )
|
|
{
|
|
selectionState.SelectTrack
|
|
( *t, false, false, mixerBoard );
|
|
trackPanel->SetFocusedTrack( n ); // move focus to next track down
|
|
trackPanel->EnsureVisible( n );
|
|
project.ModifyState(false);
|
|
return;
|
|
}
|
|
if( tSelected && !nSelected )
|
|
{
|
|
selectionState.SelectTrack
|
|
( *n, true, false, mixerBoard );
|
|
trackPanel->SetFocusedTrack( n ); // move focus to next track down
|
|
trackPanel->EnsureVisible( n );
|
|
project.ModifyState(false);
|
|
return;
|
|
}
|
|
if( !tSelected && nSelected )
|
|
{
|
|
selectionState.SelectTrack
|
|
( *n, false, false, mixerBoard );
|
|
trackPanel->SetFocusedTrack( n ); // move focus to next track down
|
|
trackPanel->EnsureVisible( n );
|
|
project.ModifyState(false);
|
|
return;
|
|
}
|
|
if( !tSelected && !nSelected )
|
|
{
|
|
selectionState.SelectTrack
|
|
( *t, true, false, mixerBoard );
|
|
trackPanel->SetFocusedTrack( n ); // move focus to next track down
|
|
trackPanel->EnsureVisible( n );
|
|
project.ModifyState(false);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto n = * ++ tracks->FindLeader( t ); // Get next track
|
|
if( n == NULL ) // On last track so stay there
|
|
{
|
|
wxBell();
|
|
if( mCircularTrackNavigation )
|
|
{
|
|
n = *tracks->Any().begin();
|
|
trackPanel->SetFocusedTrack( n ); // Wrap to the first track
|
|
trackPanel->EnsureVisible( n );
|
|
project.ModifyState(false);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
trackPanel->EnsureVisible( t );
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
trackPanel->SetFocusedTrack( n ); // move focus to next track down
|
|
trackPanel->EnsureVisible( n );
|
|
project.ModifyState(false);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::OnCursorUp(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
DoPrevTrack( project, false );
|
|
}
|
|
|
|
void MenuCommandHandler::OnCursorDown(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
DoNextTrack( project, false );
|
|
}
|
|
|
|
void MenuCommandHandler::OnFirstTrack(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto tracks = project.GetTracks();
|
|
|
|
Track *t = trackPanel->GetFocusedTrack();
|
|
if (!t)
|
|
return;
|
|
|
|
auto f = *tracks->Any().begin();
|
|
if (t != f)
|
|
{
|
|
trackPanel->SetFocusedTrack(f);
|
|
project.ModifyState(false);
|
|
}
|
|
trackPanel->EnsureVisible(f);
|
|
}
|
|
|
|
void MenuCommandHandler::OnLastTrack(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto tracks = project.GetTracks();
|
|
|
|
Track *t = trackPanel->GetFocusedTrack();
|
|
if (!t)
|
|
return;
|
|
|
|
auto l = *tracks->Any().rbegin();
|
|
if (t != l)
|
|
{
|
|
trackPanel->SetFocusedTrack(l);
|
|
project.ModifyState(false);
|
|
}
|
|
trackPanel->EnsureVisible(l);
|
|
}
|
|
|
|
void MenuCommandHandler::OnShiftUp(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
DoPrevTrack( project, true );
|
|
}
|
|
|
|
void MenuCommandHandler::OnShiftDown(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
DoNextTrack( project, true );
|
|
}
|
|
|
|
#include "TrackPanelAx.h"
|
|
void MenuCommandHandler::OnToggle(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto &selectionState = project.GetSelectionState();
|
|
auto mixerBoard = project.GetMixerBoard();
|
|
|
|
Track *t;
|
|
|
|
t = trackPanel->GetFocusedTrack(); // Get currently focused track
|
|
if (!t)
|
|
return;
|
|
|
|
selectionState.SelectTrack
|
|
( *t, !t->GetSelected(), true, mixerBoard );
|
|
trackPanel->EnsureVisible( t );
|
|
project.ModifyState(false);
|
|
|
|
trackPanel->GetAx().Updated();
|
|
|
|
return;
|
|
}
|
|
|
|
void MenuCommandHandler::NextOrPrevFrame(AudacityProject &project, bool forward)
|
|
{
|
|
// Focus won't take in a dock unless at least one descendant window
|
|
// accepts focus. Tell controls to take focus for the duration of this
|
|
// function, only. Outside of this, they won't steal the focus when
|
|
// clicked.
|
|
auto temp1 = AButton::TemporarilyAllowFocus();
|
|
auto temp2 = ASlider::TemporarilyAllowFocus();
|
|
auto temp3 = MeterPanel::TemporarilyAllowFocus();
|
|
|
|
auto toolManager = project.GetToolManager();
|
|
auto botDock = toolManager->GetBotDock();
|
|
|
|
|
|
// Define the set of windows we rotate among.
|
|
static const unsigned rotationSize = 3u;
|
|
|
|
wxWindow *const begin [rotationSize] = {
|
|
project.GetTopPanel(),
|
|
project.GetTrackPanel(),
|
|
botDock,
|
|
};
|
|
|
|
const auto end = begin + rotationSize;
|
|
|
|
// helper functions
|
|
auto IndexOf = [&](wxWindow *pWindow) {
|
|
return std::find(begin, end, pWindow) - begin;
|
|
};
|
|
|
|
auto FindAncestor = [&]() {
|
|
wxWindow *pWindow = wxWindow::FindFocus();
|
|
unsigned index = rotationSize;
|
|
while ( pWindow &&
|
|
(rotationSize == (index = IndexOf(pWindow) ) ) )
|
|
pWindow = pWindow->GetParent();
|
|
return index;
|
|
};
|
|
|
|
const auto idx = FindAncestor();
|
|
if (idx == rotationSize)
|
|
return;
|
|
|
|
auto idx2 = idx;
|
|
auto increment = (forward ? 1 : rotationSize - 1);
|
|
|
|
while( idx != (idx2 = (idx2 + increment) % rotationSize) ) {
|
|
wxWindow *toFocus = begin[idx2];
|
|
bool bIsAnEmptyDock=false;
|
|
if( idx2 != 1 )
|
|
bIsAnEmptyDock = ((idx2==0) ? toolManager->GetTopDock() : botDock)->
|
|
GetChildren().GetCount() < 1;
|
|
|
|
// Skip docks that are empty (Bug 1564).
|
|
if( !bIsAnEmptyDock ){
|
|
toFocus->SetFocus();
|
|
if ( FindAncestor() == idx2 )
|
|
// The focus took!
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::OnNextFrame(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
NextOrPrevFrame(project, true);
|
|
}
|
|
|
|
void MenuCommandHandler::OnPrevFrame(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
NextOrPrevFrame(project, false);
|
|
}
|
|
|
|
void MenuCommandHandler::OnNextWindow(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto isEnabled = project.IsEnabled();
|
|
|
|
wxWindow *w = wxGetTopLevelParent(wxWindow::FindFocus());
|
|
const auto & list = project.GetChildren();
|
|
auto iter = list.begin(), end = list.end();
|
|
|
|
// If the project window has the current focus, start the search with the first child
|
|
if (w == &project)
|
|
{
|
|
}
|
|
// Otherwise start the search with the current window's next sibling
|
|
else
|
|
{
|
|
// Find the window in this projects children. If the window with the
|
|
// focus isn't a child of this project (like when a dialog is created
|
|
// without specifying a parent), then we'll get back NULL here.
|
|
while (iter != end && *iter != w)
|
|
++iter;
|
|
if (iter != end)
|
|
++iter;
|
|
}
|
|
|
|
// Search for the next toplevel window
|
|
for (; iter != end; ++iter)
|
|
{
|
|
// If it's a toplevel, visible (we have hidden windows) and is enabled,
|
|
// then we're done. The IsEnabled() prevents us from moving away from
|
|
// a modal dialog because all other toplevel windows will be disabled.
|
|
w = *iter;
|
|
if (w->IsTopLevel() && w->IsShown() && w->IsEnabled())
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Ran out of siblings, so make the current project active
|
|
if ((iter == end) && isEnabled)
|
|
{
|
|
w = &project;
|
|
}
|
|
|
|
// And make sure it's on top (only for floating windows...project window will not raise)
|
|
// (Really only works on Windows)
|
|
w->Raise();
|
|
|
|
|
|
#if defined(__WXMAC__) || defined(__WXGTK__)
|
|
// bug 868
|
|
// Simulate a TAB key press before continuing, else the cycle of
|
|
// navigation among top level windows stops because the keystrokes don't
|
|
// go to the CommandManager.
|
|
if (dynamic_cast<wxDialog*>(w)) {
|
|
w->SetFocus();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void MenuCommandHandler::OnPrevWindow(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto isEnabled = project.IsEnabled();
|
|
|
|
wxWindow *w = wxGetTopLevelParent(wxWindow::FindFocus());
|
|
const auto & list = project.GetChildren();
|
|
auto iter = list.rbegin(), end = list.rend();
|
|
|
|
// If the project window has the current focus, start the search with the last child
|
|
if (w == &project)
|
|
{
|
|
}
|
|
// Otherwise start the search with the current window's previous sibling
|
|
else
|
|
{
|
|
while (iter != end && *iter != w)
|
|
++iter;
|
|
if (iter != end)
|
|
++iter;
|
|
}
|
|
|
|
// Search for the previous toplevel window
|
|
for (; iter != end; ++iter)
|
|
{
|
|
// If it's a toplevel and is visible (we have come hidden windows), then we're done
|
|
w = *iter;
|
|
if (w->IsTopLevel() && w->IsShown() && isEnabled)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Ran out of siblings, so make the current project active
|
|
if ((iter == end) && isEnabled)
|
|
{
|
|
w = &project;
|
|
}
|
|
|
|
// And make sure it's on top (only for floating windows...project window will not raise)
|
|
// (Really only works on Windows)
|
|
w->Raise();
|
|
|
|
|
|
#if defined(__WXMAC__) || defined(__WXGTK__)
|
|
// bug 868
|
|
// Simulate a TAB key press before continuing, else the cycle of
|
|
// navigation among top level windows stops because the keystrokes don't
|
|
// go to the CommandManager.
|
|
if (dynamic_cast<wxDialog*>(w)) {
|
|
w->SetFocus();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
///The following methods operate controls on specified tracks,
|
|
///This will pop up the track panning dialog for specified track
|
|
void MenuCommandHandler::OnTrackPan(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto trackPanel = project.GetTrackPanel();
|
|
|
|
Track *const track = trackPanel->GetFocusedTrack();
|
|
if (track) track->TypeSwitch( [&](WaveTrack *wt) {
|
|
LWSlider *slider = trackPanel->PanSlider(wt);
|
|
if (slider->ShowDialog())
|
|
project.SetTrackPan(wt, slider);
|
|
});
|
|
}
|
|
|
|
void MenuCommandHandler::OnTrackPanLeft(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto trackPanel = project.GetTrackPanel();
|
|
|
|
Track *const track = trackPanel->GetFocusedTrack();
|
|
if (track) track->TypeSwitch( [&](WaveTrack *wt) {
|
|
LWSlider *slider = trackPanel->PanSlider(wt);
|
|
slider->Decrease(1);
|
|
project.SetTrackPan(wt, slider);
|
|
});
|
|
}
|
|
|
|
void MenuCommandHandler::OnTrackPanRight(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto trackPanel = project.GetTrackPanel();
|
|
|
|
Track *const track = trackPanel->GetFocusedTrack();
|
|
if (track) track->TypeSwitch( [&](WaveTrack *wt) {
|
|
LWSlider *slider = trackPanel->PanSlider(wt);
|
|
slider->Increase(1);
|
|
project.SetTrackPan(wt, slider);
|
|
});
|
|
}
|
|
|
|
void MenuCommandHandler::OnTrackGain(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto trackPanel = project.GetTrackPanel();
|
|
|
|
/// This will pop up the track gain dialog for specified track
|
|
Track *const track = trackPanel->GetFocusedTrack();
|
|
if (track) track->TypeSwitch( [&](WaveTrack *wt) {
|
|
LWSlider *slider = trackPanel->GainSlider(wt);
|
|
if (slider->ShowDialog())
|
|
project.SetTrackGain(wt, slider);
|
|
});
|
|
}
|
|
|
|
void MenuCommandHandler::OnTrackGainInc(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto trackPanel = project.GetTrackPanel();
|
|
|
|
Track *const track = trackPanel->GetFocusedTrack();
|
|
if (track) track->TypeSwitch( [&](WaveTrack *wt) {
|
|
LWSlider *slider = trackPanel->GainSlider(wt);
|
|
slider->Increase(1);
|
|
project.SetTrackGain(wt, slider);
|
|
});
|
|
}
|
|
|
|
void MenuCommandHandler::OnTrackGainDec(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto trackPanel = project.GetTrackPanel();
|
|
|
|
Track *const track = trackPanel->GetFocusedTrack();
|
|
if (track) track->TypeSwitch( [&](WaveTrack *wt) {
|
|
LWSlider *slider = trackPanel->GainSlider(wt);
|
|
slider->Decrease(1);
|
|
project.SetTrackGain(wt, slider);
|
|
});
|
|
}
|
|
|
|
void MenuCommandHandler::OnTrackMenu(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto trackPanel = project.GetTrackPanel();
|
|
|
|
trackPanel->OnTrackMenu();
|
|
}
|
|
|
|
void MenuCommandHandler::OnTrackMute(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto trackPanel = project.GetTrackPanel();
|
|
|
|
const auto track = trackPanel->GetFocusedTrack();
|
|
if (track) track->TypeSwitch( [&](PlayableTrack *t) {
|
|
project.DoTrackMute(t, false);
|
|
});
|
|
}
|
|
|
|
void MenuCommandHandler::OnTrackSolo(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto trackPanel = project.GetTrackPanel();
|
|
|
|
const auto track = trackPanel->GetFocusedTrack();
|
|
if (track) track->TypeSwitch( [&](PlayableTrack *t) {
|
|
project.DoTrackSolo(t, false);
|
|
});
|
|
}
|
|
|
|
void MenuCommandHandler::OnTrackClose(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto trackPanel = project.GetTrackPanel();
|
|
|
|
Track *t = trackPanel->GetFocusedTrack();
|
|
if (!t)
|
|
return;
|
|
|
|
auto isAudioActive = project.IsAudioActive();
|
|
|
|
if (isAudioActive)
|
|
{
|
|
project.TP_DisplayStatusMessage(_("Can't delete track with active audio"));
|
|
wxBell();
|
|
return;
|
|
}
|
|
|
|
project.RemoveTrack(t);
|
|
|
|
trackPanel->UpdateViewIfNoTracks();
|
|
trackPanel->Refresh(false);
|
|
}
|
|
|
|
void MenuCommandHandler::OnTrackMoveUp(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto tracks = project.GetTracks();
|
|
|
|
Track *const focusedTrack = trackPanel->GetFocusedTrack();
|
|
if (tracks->CanMoveUp(focusedTrack)) {
|
|
MoveTrack(project, focusedTrack, OnMoveUpID);
|
|
trackPanel->Refresh(false);
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::OnTrackMoveDown(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto tracks = project.GetTracks();
|
|
|
|
Track *const focusedTrack = trackPanel->GetFocusedTrack();
|
|
if (tracks->CanMoveDown(focusedTrack)) {
|
|
MoveTrack(project, focusedTrack, OnMoveDownID);
|
|
trackPanel->Refresh(false);
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::OnTrackMoveTop(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto tracks = project.GetTracks();
|
|
|
|
Track *const focusedTrack = trackPanel->GetFocusedTrack();
|
|
if (tracks->CanMoveUp(focusedTrack)) {
|
|
MoveTrack(project, focusedTrack, OnMoveTopID);
|
|
trackPanel->Refresh(false);
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::OnTrackMoveBottom(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto tracks = project.GetTracks();
|
|
|
|
Track *const focusedTrack = trackPanel->GetFocusedTrack();
|
|
if (tracks->CanMoveDown(focusedTrack)) {
|
|
MoveTrack(project, focusedTrack, OnMoveBottomID);
|
|
trackPanel->Refresh(false);
|
|
}
|
|
}
|
|
|
|
/// Move a track up, down, to top or to bottom.
|
|
|
|
void MenuCommandHandler::MoveTrack
|
|
(AudacityProject &project, Track* target, MoveChoice choice)
|
|
{
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto tracks = project.GetTracks();
|
|
auto mixerBoard = project.GetMixerBoard(); // Update mixer board.
|
|
|
|
wxString longDesc, shortDesc;
|
|
|
|
auto pt = dynamic_cast<PlayableTrack*>(target);
|
|
switch (choice)
|
|
{
|
|
case OnMoveTopID:
|
|
/* i18n-hint: Past tense of 'to move', as in 'moved audio track up'.*/
|
|
longDesc = _("Moved '%s' to Top");
|
|
shortDesc = _("Move Track to Top");
|
|
|
|
while (tracks->CanMoveUp(target)) {
|
|
if (tracks->Move(target, true)) {
|
|
if (mixerBoard && pt)
|
|
mixerBoard->MoveTrackCluster(pt, true);
|
|
}
|
|
}
|
|
break;
|
|
case OnMoveBottomID:
|
|
/* i18n-hint: Past tense of 'to move', as in 'moved audio track up'.*/
|
|
longDesc = _("Moved '%s' to Bottom");
|
|
shortDesc = _("Move Track to Bottom");
|
|
|
|
while (tracks->CanMoveDown(target)) {
|
|
if(tracks->Move(target, false)) {
|
|
if (mixerBoard && pt)
|
|
mixerBoard->MoveTrackCluster(pt, false);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
bool bUp = (OnMoveUpID == choice);
|
|
|
|
if (tracks->Move(target, bUp)) {
|
|
if (mixerBoard && pt)
|
|
mixerBoard->MoveTrackCluster(pt, bUp);
|
|
}
|
|
longDesc =
|
|
/* i18n-hint: Past tense of 'to move', as in 'moved audio track up'.*/
|
|
bUp? _("Moved '%s' Up")
|
|
: _("Moved '%s' Down");
|
|
shortDesc =
|
|
/* i18n-hint: Past tense of 'to move', as in 'moved audio track up'.*/
|
|
bUp? _("Move Track Up")
|
|
: _("Move Track Down");
|
|
|
|
}
|
|
|
|
longDesc = longDesc.Format(target->GetName());
|
|
|
|
project.PushState(longDesc, shortDesc);
|
|
trackPanel->Refresh(false);
|
|
}
|
|
|
|
void MenuCommandHandler::OnInputDevice(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tb = project.GetDeviceToolBar();
|
|
|
|
if (tb) {
|
|
tb->ShowInputDialog();
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::OnOutputDevice(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tb = project.GetDeviceToolBar();
|
|
|
|
if (tb) {
|
|
tb->ShowOutputDialog();
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::OnAudioHost(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tb = project.GetDeviceToolBar();
|
|
|
|
if (tb) {
|
|
tb->ShowHostDialog();
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::OnInputChannels(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tb = project.GetDeviceToolBar();
|
|
|
|
if (tb) {
|
|
tb->ShowChannelsDialog();
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::OnOutputGain(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tb = project.GetMixerToolBar();
|
|
|
|
if (tb) {
|
|
tb->ShowOutputGainDialog();
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::OnInputGain(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tb = project.GetMixerToolBar();
|
|
|
|
if (tb) {
|
|
tb->ShowInputGainDialog();
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::OnOutputGainInc(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tb = project.GetMixerToolBar();
|
|
|
|
if (tb) {
|
|
tb->AdjustOutputGain(1);
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::OnOutputGainDec(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tb = project.GetMixerToolBar();
|
|
|
|
if (tb) {
|
|
tb->AdjustOutputGain(-1);
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::OnInputGainInc(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tb = project.GetMixerToolBar();
|
|
|
|
if (tb) {
|
|
tb->AdjustInputGain(1);
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::OnInputGainDec(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tb = project.GetMixerToolBar();
|
|
|
|
if (tb) {
|
|
tb->AdjustInputGain(-1);
|
|
}
|
|
}
|
|
|
|
/// DoAudacityCommand() takes a PluginID and executes the assocated effect.
|
|
///
|
|
/// At the moment flags are used only to indicate whether to prompt for parameters,
|
|
bool MenuCommandHandler::DoAudacityCommand(const PluginID & ID, const CommandContext & context, int flags)
|
|
{
|
|
auto &project = context.project;
|
|
const PluginDescriptor *plug = PluginManager::Get().GetPlugin(ID);
|
|
if (!plug)
|
|
return false;
|
|
|
|
if (flags & OnEffectFlags::kConfigured)
|
|
{
|
|
TransportActions::DoStop(project);
|
|
// SelectAllIfNone();
|
|
}
|
|
|
|
EffectManager & em = EffectManager::Get();
|
|
bool success = em.DoAudacityCommand(ID,
|
|
context,
|
|
&project,
|
|
(flags & OnEffectFlags::kConfigured) == 0);
|
|
|
|
if (!success)
|
|
return false;
|
|
|
|
/*
|
|
if (em.GetSkipStateFlag())
|
|
flags = flags | OnEffectFlags::kSkipState;
|
|
|
|
if (!(flags & OnEffectFlags::kSkipState))
|
|
{
|
|
wxString shortDesc = em.GetCommandName(ID);
|
|
wxString longDesc = em.GetCommandDescription(ID);
|
|
PushState(longDesc, shortDesc);
|
|
}
|
|
*/
|
|
project.RedrawProject();
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Effect Menus
|
|
//
|
|
|
|
/// DoEffect() takes a PluginID and has the EffectManager execute the assocated effect.
|
|
///
|
|
/// At the moment flags are used only to indicate whether to prompt for parameters,
|
|
/// whether to save the state to history and whether to allow 'Repeat Last Effect'.
|
|
bool MenuCommandHandler::DoEffect(
|
|
const PluginID & ID, const CommandContext &context, int flags)
|
|
{
|
|
AudacityProject &project = context.project;
|
|
auto tracks = project.GetTracks();
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto trackFactory = project.GetTrackFactory();
|
|
auto rate = project.GetRate();
|
|
auto &selectedRegion = project.GetSelection();
|
|
auto commandManager = project.GetCommandManager();
|
|
|
|
const PluginDescriptor *plug = PluginManager::Get().GetPlugin(ID);
|
|
if (!plug)
|
|
return false;
|
|
|
|
EffectType type = plug->GetEffectType();
|
|
|
|
// Make sure there's no activity since the effect is about to be applied
|
|
// to the project's tracks. Mainly for Apply during RTP, but also used
|
|
// for batch commands
|
|
if (flags & MenuCommandHandler::OnEffectFlags::kConfigured)
|
|
{
|
|
TransportActions::DoStop(project);
|
|
project.SelectAllIfNone();
|
|
}
|
|
|
|
wxGetApp().SetMissingAliasedFileWarningShouldShow(true);
|
|
|
|
auto nTracksOriginally = project.GetTrackCount();
|
|
wxWindow *focus = wxWindow::FindFocus();
|
|
wxWindow *parent = nullptr;
|
|
if (focus != nullptr) {
|
|
parent = focus->GetParent();
|
|
}
|
|
|
|
bool success = false;
|
|
auto cleanup = finally( [&] {
|
|
|
|
if (!success) {
|
|
// For now, we're limiting realtime preview to a single effect, so
|
|
// make sure the menus reflect that fact that one may have just been
|
|
// opened.
|
|
GetMenuManager(project).UpdateMenus(project, false);
|
|
}
|
|
|
|
} );
|
|
|
|
int count = 0;
|
|
bool clean = true;
|
|
for (auto t : tracks->Selected< const WaveTrack >()) {
|
|
if (t->GetEndTime() != 0.0)
|
|
clean = false;
|
|
count++;
|
|
}
|
|
|
|
EffectManager & em = EffectManager::Get();
|
|
|
|
success = em.DoEffect(ID, &project, rate,
|
|
tracks, trackFactory, &selectedRegion,
|
|
(flags & MenuCommandHandler::OnEffectFlags::kConfigured) == 0);
|
|
|
|
if (!success)
|
|
return false;
|
|
|
|
if (em.GetSkipStateFlag())
|
|
flags = flags | MenuCommandHandler::OnEffectFlags::kSkipState;
|
|
|
|
if (!(flags & MenuCommandHandler::OnEffectFlags::kSkipState))
|
|
{
|
|
wxString shortDesc = em.GetCommandName(ID);
|
|
wxString longDesc = em.GetCommandDescription(ID);
|
|
project.PushState(longDesc, shortDesc);
|
|
}
|
|
|
|
if (!(flags & MenuCommandHandler::OnEffectFlags::kDontRepeatLast))
|
|
{
|
|
// Only remember a successful effect, don't remember insert,
|
|
// or analyze effects.
|
|
if (type == EffectTypeProcess) {
|
|
wxString shortDesc = em.GetCommandName(ID);
|
|
GetMenuManager(project).mLastEffect = ID;
|
|
wxString lastEffectDesc;
|
|
/* i18n-hint: %s will be the name of the effect which will be
|
|
* repeated if this menu item is chosen */
|
|
lastEffectDesc.Printf(_("Repeat %s"), shortDesc);
|
|
commandManager->Modify(wxT("RepeatLastEffect"), lastEffectDesc);
|
|
}
|
|
}
|
|
|
|
//STM:
|
|
//The following automatically re-zooms after sound was generated.
|
|
// IMO, it was disorienting, removing to try out without re-fitting
|
|
//mchinen:12/14/08 reapplying for generate effects
|
|
if (type == EffectTypeGenerate)
|
|
{
|
|
if (count == 0 || (clean && selectedRegion.t0() == 0.0))
|
|
ViewActions::DoZoomFit(project);
|
|
// trackPanel->Refresh(false);
|
|
}
|
|
project.RedrawProject();
|
|
if (focus != nullptr && focus->GetParent()==parent) {
|
|
focus->SetFocus();
|
|
}
|
|
|
|
// A fix for Bug 63
|
|
// New tracks added? Scroll them into view so that user sees them.
|
|
// Don't care what track type. An analyser might just have added a
|
|
// Label track and we want to see it.
|
|
if( project.GetTrackCount() > nTracksOriginally ){
|
|
// 0.0 is min scroll position, 1.0 is max scroll position.
|
|
trackPanel->VerticalScroll( 1.0 );
|
|
} else {
|
|
trackPanel->EnsureVisible(trackPanel->GetFirstSelectedTrack());
|
|
trackPanel->Refresh(false);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void MenuCommandHandler::OnEffect(const CommandContext &context)
|
|
{
|
|
DoEffect(context.parameter, context, 0);
|
|
}
|
|
|
|
void MenuCommandHandler::OnRepeatLastEffect(const CommandContext &context)
|
|
{
|
|
auto lastEffect = GetMenuManager(context.project).mLastEffect;
|
|
if (!lastEffect.IsEmpty())
|
|
{
|
|
DoEffect(lastEffect,
|
|
context, OnEffectFlags::kConfigured);
|
|
}
|
|
}
|
|
|
|
|
|
void MenuCommandHandler::RebuildAllMenuBars()
|
|
{
|
|
for( size_t i = 0; i < gAudacityProjects.size(); i++ ) {
|
|
AudacityProject *p = gAudacityProjects[i].get();
|
|
|
|
GetMenuManager(*p).RebuildMenuBar(*p);
|
|
#if defined(__WXGTK__)
|
|
// Workaround for:
|
|
//
|
|
// http://bugzilla.audacityteam.org/show_bug.cgi?id=458
|
|
//
|
|
// This workaround should be removed when Audacity updates to wxWidgets 3.x which has a fix.
|
|
wxRect r = p->GetRect();
|
|
p->SetSize(wxSize(1,1));
|
|
p->SetSize(r.GetSize());
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::DoManagePluginsMenu
|
|
(AudacityProject &project, EffectType type)
|
|
{
|
|
if (PluginManager::Get().ShowManager(&project, type))
|
|
RebuildAllMenuBars();
|
|
}
|
|
|
|
void MenuCommandHandler::OnManageGenerators(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
DoManagePluginsMenu(project, EffectTypeGenerate);
|
|
}
|
|
|
|
void MenuCommandHandler::OnManageEffects(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
DoManagePluginsMenu(project, EffectTypeProcess);
|
|
}
|
|
|
|
void MenuCommandHandler::OnManageAnalyzers(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
DoManagePluginsMenu(project, EffectTypeAnalyze);
|
|
}
|
|
|
|
void MenuCommandHandler::OnManageTools(const CommandContext &context )
|
|
{
|
|
auto &project = context.project;
|
|
DoManagePluginsMenu(project, EffectTypeTool);
|
|
}
|
|
|
|
|
|
void MenuCommandHandler::OnStereoToMono(const CommandContext &context)
|
|
{
|
|
DoEffect(EffectManager::Get().GetEffectByIdentifier(wxT("StereoToMono")),
|
|
context,
|
|
OnEffectFlags::kConfigured);
|
|
}
|
|
|
|
void MenuCommandHandler::OnAudacityCommand(const CommandContext & ctx)
|
|
{
|
|
wxLogDebug( "Command was: %s", ctx.parameter);
|
|
DoAudacityCommand(EffectManager::Get().GetEffectByIdentifier(ctx.parameter),
|
|
ctx,
|
|
OnEffectFlags::kNone); // Not configured, so prompt user.
|
|
}
|
|
|
|
//
|
|
// File Menu
|
|
//
|
|
|
|
void MenuCommandHandler::OnCheckDependencies(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
::ShowDependencyDialogIfNeeded(&project, false);
|
|
}
|
|
|
|
//
|
|
// Edit Menu
|
|
//
|
|
|
|
|
|
void AudacityProject::SelectNone()
|
|
{
|
|
for (auto t : GetTracks()->Any())
|
|
t->SetSelected(false);
|
|
|
|
mTrackPanel->Refresh(false);
|
|
if (mMixerBoard)
|
|
mMixerBoard->Refresh(false);
|
|
}
|
|
|
|
//
|
|
// View Menu
|
|
//
|
|
|
|
double AudacityProject::GetScreenEndTime() const
|
|
{
|
|
return mTrackPanel->GetScreenEndTime();
|
|
}
|
|
|
|
void AudacityProject::ZoomInByFactor( double ZoomFactor )
|
|
{
|
|
// LLL: Handling positioning differently when audio is
|
|
// actively playing. Don't do this if paused.
|
|
if ((gAudioIO->IsStreamActive(GetAudioIOToken()) != 0) && !gAudioIO->IsPaused()){
|
|
ZoomBy(ZoomFactor);
|
|
mTrackPanel->ScrollIntoView(gAudioIO->GetStreamTime());
|
|
mTrackPanel->Refresh(false);
|
|
return;
|
|
}
|
|
|
|
// DMM: Here's my attempt to get logical zooming behavior
|
|
// when there's a selection that's currently at least
|
|
// partially on-screen
|
|
|
|
const double endTime = GetScreenEndTime();
|
|
const double duration = endTime - mViewInfo.h;
|
|
|
|
bool selectionIsOnscreen =
|
|
(mViewInfo.selectedRegion.t0() < endTime) &&
|
|
(mViewInfo.selectedRegion.t1() >= mViewInfo.h);
|
|
|
|
bool selectionFillsScreen =
|
|
(mViewInfo.selectedRegion.t0() < mViewInfo.h) &&
|
|
(mViewInfo.selectedRegion.t1() > endTime);
|
|
|
|
if (selectionIsOnscreen && !selectionFillsScreen) {
|
|
// Start with the center of the selection
|
|
double selCenter = (mViewInfo.selectedRegion.t0() +
|
|
mViewInfo.selectedRegion.t1()) / 2;
|
|
|
|
// If the selection center is off-screen, pick the
|
|
// center of the part that is on-screen.
|
|
if (selCenter < mViewInfo.h)
|
|
selCenter = mViewInfo.h +
|
|
(mViewInfo.selectedRegion.t1() - mViewInfo.h) / 2;
|
|
if (selCenter > endTime)
|
|
selCenter = endTime -
|
|
(endTime - mViewInfo.selectedRegion.t0()) / 2;
|
|
|
|
// Zoom in
|
|
ZoomBy(ZoomFactor);
|
|
const double newDuration = GetScreenEndTime() - mViewInfo.h;
|
|
|
|
// Recenter on selCenter
|
|
TP_ScrollWindow(selCenter - newDuration / 2);
|
|
return;
|
|
}
|
|
|
|
|
|
double origLeft = mViewInfo.h;
|
|
double origWidth = duration;
|
|
ZoomBy(ZoomFactor);
|
|
|
|
const double newDuration = GetScreenEndTime() - mViewInfo.h;
|
|
double newh = origLeft + (origWidth - newDuration) / 2;
|
|
|
|
// MM: Commented this out because it was confusing users
|
|
/*
|
|
// make sure that the *right-hand* end of the selection is
|
|
// no further *left* than 1/3 of the way across the screen
|
|
if (mViewInfo.selectedRegion.t1() < newh + mViewInfo.screen / 3)
|
|
newh = mViewInfo.selectedRegion.t1() - mViewInfo.screen / 3;
|
|
|
|
// make sure that the *left-hand* end of the selection is
|
|
// no further *right* than 2/3 of the way across the screen
|
|
if (mViewInfo.selectedRegion.t0() > newh + mViewInfo.screen * 2 / 3)
|
|
newh = mViewInfo.selectedRegion.t0() - mViewInfo.screen * 2 / 3;
|
|
*/
|
|
|
|
TP_ScrollWindow(newh);
|
|
}
|
|
|
|
|
|
void AudacityProject::ZoomOutByFactor( double ZoomFactor )
|
|
{
|
|
//Zoom() may change these, so record original values:
|
|
const double origLeft = mViewInfo.h;
|
|
const double origWidth = GetScreenEndTime() - origLeft;
|
|
|
|
ZoomBy(ZoomFactor);
|
|
const double newWidth = GetScreenEndTime() - mViewInfo.h;
|
|
|
|
const double newh = origLeft + (origWidth - newWidth) / 2;
|
|
// newh = (newh > 0) ? newh : 0;
|
|
TP_ScrollWindow(newh);
|
|
}
|
|
|
|
void MenuCommandHandler::OnApplyMacroDirectly(const CommandContext &context )
|
|
{
|
|
auto &project = context.project;
|
|
|
|
//wxLogDebug( "Macro was: %s", context.parameter);
|
|
ApplyMacroDialog dlg( &project );
|
|
wxString Name = context.parameter;
|
|
|
|
// We used numbers previously, but macros could get renumbered, making
|
|
// macros containing macros unpredictable.
|
|
#ifdef MACROS_BY_NUMBERS
|
|
long item=0;
|
|
// Take last three letters (of e.g. Macro007) and convert to a number.
|
|
Name.Mid( Name.Length() - 3 ).ToLong( &item, 10 );
|
|
dlg.ApplyMacroToProject( item, false );
|
|
#else
|
|
dlg.ApplyMacroToProject( Name, false );
|
|
#endif
|
|
MenuManager::ModifyUndoMenuItems( project );
|
|
}
|
|
|
|
void MenuCommandHandler::OnApplyMacrosPalette(const CommandContext &context )
|
|
{
|
|
auto &project = context.project;
|
|
project.GetMacrosWindow( false, true );
|
|
}
|
|
|
|
void MenuCommandHandler::OnManageMacros(const CommandContext &context )
|
|
{
|
|
auto &project = context.project;
|
|
project.GetMacrosWindow( true, true );
|
|
}
|
|
|
|
void MenuCommandHandler::OnPlotSpectrum(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto freqWindow = project.GetFreqWindow(true);
|
|
|
|
|
|
if( ScreenshotCommand::MayCapture( freqWindow ) )
|
|
return;
|
|
freqWindow->Show(true);
|
|
freqWindow->Raise();
|
|
freqWindow->SetFocus();
|
|
}
|
|
|
|
void MenuCommandHandler::OnContrast(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto contrastDialog = project.GetContrastDialog(true);
|
|
|
|
|
|
contrastDialog->CentreOnParent();
|
|
if( ScreenshotCommand::MayCapture( contrastDialog ) )
|
|
return;
|
|
contrastDialog->Show();
|
|
}
|
|
|
|
|
|
//
|
|
// Project Menu
|
|
//
|
|
|
|
void MenuCommandHandler::HandleMixAndRender
|
|
(AudacityProject &project, bool toNewTrack)
|
|
{
|
|
auto tracks = project.GetTracks();
|
|
auto trackFactory = project.GetTrackFactory();
|
|
auto rate = project.GetRate();
|
|
auto defaultFormat = project.GetDefaultFormat();
|
|
auto trackPanel = project.GetTrackPanel();
|
|
|
|
wxGetApp().SetMissingAliasedFileWarningShouldShow(true);
|
|
|
|
WaveTrack::Holder uNewLeft, uNewRight;
|
|
::MixAndRender(
|
|
tracks, trackFactory, rate, defaultFormat, 0.0, 0.0, uNewLeft, uNewRight);
|
|
|
|
if (uNewLeft) {
|
|
// Remove originals, get stats on what tracks were mixed
|
|
|
|
auto trackRange = tracks->Selected< WaveTrack >();
|
|
auto selectedCount = (trackRange + &Track::IsLeader).size();
|
|
wxString firstName;
|
|
if (selectedCount > 0)
|
|
firstName = (*trackRange.begin())->GetName();
|
|
if (!toNewTrack) {
|
|
// Beware iterator invalidation!
|
|
for (auto &it = trackRange.first, &end = trackRange.second; it != end;)
|
|
tracks->Remove( *it++ );
|
|
}
|
|
|
|
// Add NEW tracks
|
|
|
|
auto pNewLeft = tracks->Add(std::move(uNewLeft));
|
|
decltype(pNewLeft) pNewRight{};
|
|
if (uNewRight)
|
|
pNewRight = tracks->Add(std::move(uNewRight));
|
|
|
|
// Do this only after adding tracks to the list
|
|
tracks->GroupChannels(*pNewLeft, pNewRight ? 2 : 1);
|
|
|
|
// If we're just rendering (not mixing), keep the track name the same
|
|
if (selectedCount==1) {
|
|
pNewLeft->SetName(firstName);
|
|
if (pNewRight)
|
|
pNewRight->SetName(firstName);
|
|
}
|
|
|
|
// Smart history/undo message
|
|
if (selectedCount==1) {
|
|
wxString msg;
|
|
msg.Printf(_("Rendered all audio in track '%s'"), firstName);
|
|
/* i18n-hint: Convert the audio into a more usable form, so apply
|
|
* panning and amplification and write to some external file.*/
|
|
project.PushState(msg, _("Render"));
|
|
}
|
|
else {
|
|
wxString msg;
|
|
if (pNewRight)
|
|
msg.Printf(_("Mixed and rendered %d tracks into one new stereo track"),
|
|
selectedCount);
|
|
else
|
|
msg.Printf(_("Mixed and rendered %d tracks into one new mono track"),
|
|
selectedCount);
|
|
project.PushState(msg, _("Mix and Render"));
|
|
}
|
|
|
|
trackPanel->SetFocus();
|
|
trackPanel->SetFocusedTrack(pNewLeft);
|
|
trackPanel->EnsureVisible(pNewLeft);
|
|
project.RedrawProject();
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::OnMixAndRender(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
HandleMixAndRender(project, false);
|
|
}
|
|
|
|
void MenuCommandHandler::OnMixAndRenderToNewTrack(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
HandleMixAndRender(project, true);
|
|
}
|
|
|
|
void MenuCommandHandler::HandleAlign
|
|
(AudacityProject &project, int index, bool moveSel)
|
|
{
|
|
auto tracks = project.GetTracks();
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
|
|
wxString action;
|
|
wxString shortAction;
|
|
double delta = 0.0;
|
|
double newPos = -1.0;
|
|
|
|
auto channelRange = tracks->Selected< AudioTrack >();
|
|
auto trackRange = tracks->SelectedLeaders< AudioTrack >();
|
|
|
|
auto FindOffset = []( const Track *pTrack ) {
|
|
return TrackList::Channels(pTrack).min( &Track::GetOffset ); };
|
|
|
|
auto firstTrackOffset = [&]{ return FindOffset( *trackRange.begin() ); };
|
|
auto minOffset = [&]{ return trackRange.min( FindOffset ); };
|
|
auto avgOffset = [&]{
|
|
return trackRange.sum( FindOffset ) /
|
|
std::max( size_t(1), trackRange.size() ); };
|
|
|
|
auto maxEndOffset = [&]{
|
|
return std::max(0.0, channelRange.max( &Track::GetEndTime ) ); };
|
|
|
|
switch(index) {
|
|
case kAlignStartZero:
|
|
delta = -minOffset();
|
|
action = moveSel
|
|
/* i18n-hint: In this and similar messages describing editing actions,
|
|
the starting or ending points of tracks are re-"aligned" to other
|
|
times, and the time selection may be "moved" too. The first
|
|
noun -- "start" in this example -- is the object of a verb (not of
|
|
an implied preposition "from"). */
|
|
? _("Aligned/Moved start to zero")
|
|
: _("Aligned start to zero");
|
|
/* i18n-hint: This and similar messages give shorter descriptions of
|
|
the aligning and moving editing actions */
|
|
shortAction = moveSel
|
|
? _("Align/Move Start")
|
|
: _("Align Start");
|
|
break;
|
|
case kAlignStartSelStart:
|
|
delta = selectedRegion.t0() - minOffset();
|
|
action = moveSel
|
|
? _("Aligned/Moved start to cursor/selection start")
|
|
: _("Aligned start to cursor/selection start");
|
|
shortAction = moveSel
|
|
? _("Align/Move Start")
|
|
: _("Align Start");
|
|
break;
|
|
case kAlignStartSelEnd:
|
|
delta = selectedRegion.t1() - minOffset();
|
|
action = moveSel
|
|
? _("Aligned/Moved start to selection end")
|
|
: _("Aligned start to selection end");
|
|
shortAction = moveSel
|
|
? _("Align/Move Start")
|
|
: _("Align Start");
|
|
break;
|
|
case kAlignEndSelStart:
|
|
delta = selectedRegion.t0() - maxEndOffset();
|
|
action = moveSel
|
|
? _("Aligned/Moved end to cursor/selection start")
|
|
: _("Aligned end to cursor/selection start");
|
|
shortAction =
|
|
moveSel
|
|
? _("Align/Move End")
|
|
: _("Align End");
|
|
break;
|
|
case kAlignEndSelEnd:
|
|
delta = selectedRegion.t1() - maxEndOffset();
|
|
action = moveSel
|
|
? _("Aligned/Moved end to selection end")
|
|
: _("Aligned end to selection end");
|
|
shortAction =
|
|
moveSel
|
|
? _("Align/Move End")
|
|
: _("Align End");
|
|
break;
|
|
// index set in alignLabelsNoSync
|
|
case kAlignEndToEnd:
|
|
newPos = firstTrackOffset();
|
|
action = moveSel
|
|
? _("Aligned/Moved end to end")
|
|
: _("Aligned end to end");
|
|
shortAction =
|
|
moveSel
|
|
? _("Align/Move End to End")
|
|
: _("Align End to End");
|
|
break;
|
|
case kAlignTogether:
|
|
newPos = avgOffset();
|
|
action = moveSel
|
|
? _("Aligned/Moved together")
|
|
: _("Aligned together");
|
|
shortAction =
|
|
moveSel
|
|
? _("Align/Move Together")
|
|
: _("Align Together");
|
|
}
|
|
|
|
if ((unsigned)index >= kAlignLabelsCount) { // This is an alignLabelsNoSync command.
|
|
for (auto t : tracks->SelectedLeaders< AudioTrack >()) {
|
|
// This shifts different tracks in different ways, so no sync-lock move.
|
|
// Only align Wave and Note tracks end to end.
|
|
auto channels = TrackList::Channels(t);
|
|
|
|
auto trackStart = channels.min( &Track::GetStartTime );
|
|
auto trackEnd = channels.max( &Track::GetEndTime );
|
|
|
|
for (auto channel : channels)
|
|
// Move the track
|
|
channel->SetOffset(newPos + channel->GetStartTime() - trackStart);
|
|
|
|
if (index == kAlignEndToEnd)
|
|
newPos += (trackEnd - trackStart);
|
|
}
|
|
if (index == kAlignEndToEnd) {
|
|
ViewActions::DoZoomFit(project);
|
|
}
|
|
}
|
|
|
|
if (delta != 0.0) {
|
|
// For a fixed-distance shift move sync-lock selected tracks also.
|
|
for (auto t : tracks->Any() + &Track::IsSelectedOrSyncLockSelected )
|
|
t->SetOffset(t->GetOffset() + delta);
|
|
}
|
|
|
|
if (moveSel)
|
|
selectedRegion.move(delta);
|
|
|
|
project.PushState(action, shortAction);
|
|
|
|
project.RedrawProject();
|
|
}
|
|
|
|
void MenuCommandHandler::OnAlignNoSync(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
|
|
// Add length of alignLabels array so that we can handle this in AudacityProject::HandleAlign.
|
|
HandleAlign(project,
|
|
context.index + kAlignLabelsCount, false);
|
|
}
|
|
|
|
void MenuCommandHandler::OnAlign(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
|
|
bool bMoveWith;
|
|
gPrefs->Read(wxT("/GUI/MoveSelectionWithTracks"), &bMoveWith, false);
|
|
HandleAlign(project, context.index, bMoveWith);
|
|
}
|
|
/*
|
|
// Now handled in OnAlign.
|
|
void AudacityProject::OnAlignMoveSel(int index)
|
|
{
|
|
HandleAlign(index, true);
|
|
}
|
|
*/
|
|
|
|
#ifdef EXPERIMENTAL_SCOREALIGN
|
|
// rough relative amount of time to compute one
|
|
// frame of audio or midi, or one cell of matrix, or one iteration
|
|
// of smoothing, measured on a 1.9GHz Core 2 Duo in 32-bit mode
|
|
// (see COLLECT_TIMING_DATA below)
|
|
#define AUDIO_WORK_UNIT 0.004F
|
|
#define MIDI_WORK_UNIT 0.0001F
|
|
#define MATRIX_WORK_UNIT 0.000002F
|
|
#define SMOOTHING_WORK_UNIT 0.000001F
|
|
|
|
// Write timing data to a file; useful for calibrating AUDIO_WORK_UNIT,
|
|
// MIDI_WORK_UNIT, MATRIX_WORK_UNIT, and SMOOTHING_WORK_UNIT coefficients
|
|
// Data is written to timing-data.txt; look in
|
|
// audacity-src/win/Release/modules/
|
|
#define COLLECT_TIMING_DATA
|
|
|
|
// Audacity Score Align Progress class -- progress reports come here
|
|
class ASAProgress final : public SAProgress {
|
|
private:
|
|
float mTotalWork;
|
|
float mFrames[2];
|
|
long mTotalCells; // how many matrix cells?
|
|
long mCellCount; // how many cells so far?
|
|
long mPrevCellCount; // cell_count last reported with Update()
|
|
Maybe<ProgressDialog> mProgress;
|
|
#ifdef COLLECT_TIMING_DATA
|
|
FILE *mTimeFile;
|
|
wxDateTime mStartTime;
|
|
long iterations;
|
|
#endif
|
|
|
|
public:
|
|
ASAProgress() {
|
|
smoothing = false;
|
|
#ifdef COLLECT_TIMING_DATA
|
|
mTimeFile = fopen("timing-data.txt", "w");
|
|
#endif
|
|
}
|
|
~ASAProgress() {
|
|
#ifdef COLLECT_TIMING_DATA
|
|
fclose(mTimeFile);
|
|
#endif
|
|
}
|
|
void set_phase(int i) override {
|
|
float work[2]; // chromagram computation work estimates
|
|
float work2, work3 = 0; // matrix and smoothing work estimates
|
|
SAProgress::set_phase(i);
|
|
#ifdef COLLECT_TIMING_DATA
|
|
long ms = 0;
|
|
wxDateTime now = wxDateTime::UNow();
|
|
wxFprintf(mTimeFile, "Phase %d begins at %s\n",
|
|
i, now.FormatTime());
|
|
if (i != 0)
|
|
ms = now.Subtract(mStartTime).GetMilliseconds().ToLong();
|
|
mStartTime = now;
|
|
#endif
|
|
if (i == 0) {
|
|
mCellCount = 0;
|
|
for (int j = 0; j < 2; j++) {
|
|
mFrames[j] = durations[j] / frame_period;
|
|
}
|
|
mTotalWork = 0;
|
|
for (int j = 0; j < 2; j++) {
|
|
work[j] =
|
|
(is_audio[j] ? AUDIO_WORK_UNIT : MIDI_WORK_UNIT) * mFrames[j];
|
|
mTotalWork += work[j];
|
|
}
|
|
mTotalCells = mFrames[0] * mFrames[1];
|
|
work2 = mTotalCells * MATRIX_WORK_UNIT;
|
|
mTotalWork += work2;
|
|
// arbitarily assume 60 iterations to fit smooth segments and
|
|
// per frame per iteration is SMOOTHING_WORK_UNIT
|
|
if (smoothing) {
|
|
work3 =
|
|
wxMax(mFrames[0], mFrames[1]) * SMOOTHING_WORK_UNIT * 40;
|
|
mTotalWork += work3;
|
|
}
|
|
#ifdef COLLECT_TIMING_DATA
|
|
wxFprintf(mTimeFile, " mTotalWork (an estimate) = %g\n", mTotalWork);
|
|
wxFprintf(mTimeFile, " work0 = %g, frames %g, is_audio %d\n",
|
|
work[0], mFrames[0], is_audio[0]);
|
|
wxFprintf(mTimeFile, " work1 = %g, frames %g, is_audio %d\n",
|
|
work[1], mFrames[1], is_audio[1]);
|
|
wxFprintf(mTimeFile, "work2 = %g, work3 = %g\n", work2, work3);
|
|
#endif
|
|
mProgress.create(_("Synchronize MIDI with Audio"),
|
|
_("Synchronizing MIDI and Audio Tracks"));
|
|
} else if (i < 3) {
|
|
wxFprintf(mTimeFile,
|
|
"Phase %d took %d ms for %g frames, coefficient = %g s/frame\n",
|
|
i - 1, ms, mFrames[i - 1], (ms * 0.001) / mFrames[i - 1]);
|
|
} else if (i == 3) {
|
|
wxFprintf(mTimeFile,
|
|
"Phase 2 took %d ms for %d cells, coefficient = %g s/cell\n",
|
|
ms, mCellCount, (ms * 0.001) / mCellCount);
|
|
} else if (i == 4) {
|
|
wxFprintf(mTimeFile, "Phase 3 took %d ms for %d iterations on %g frames, coefficient = %g s per frame per iteration\n",
|
|
ms, iterations, wxMax(mFrames[0], mFrames[1]),
|
|
(ms * 0.001) / (wxMax(mFrames[0], mFrames[1]) * iterations));
|
|
}
|
|
}
|
|
bool set_feature_progress(float s) override {
|
|
float work;
|
|
if (phase == 0) {
|
|
float f = s / frame_period;
|
|
work = (is_audio[0] ? AUDIO_WORK_UNIT : MIDI_WORK_UNIT) * f;
|
|
} else if (phase == 1) {
|
|
float f = s / frame_period;
|
|
work = (is_audio[0] ? AUDIO_WORK_UNIT : MIDI_WORK_UNIT) * mFrames[0] +
|
|
(is_audio[1] ? AUDIO_WORK_UNIT : MIDI_WORK_UNIT) * f;
|
|
}
|
|
auto updateResult = mProgress->Update((int)(work), (int)(mTotalWork));
|
|
return (updateResult == ProgressResult::Success);
|
|
}
|
|
bool set_matrix_progress(int cells) override {
|
|
mCellCount += cells;
|
|
float work =
|
|
(is_audio[0] ? AUDIO_WORK_UNIT : MIDI_WORK_UNIT) * mFrames[0] +
|
|
(is_audio[1] ? AUDIO_WORK_UNIT : MIDI_WORK_UNIT) * mFrames[1];
|
|
work += mCellCount * MATRIX_WORK_UNIT;
|
|
auto updateResult = mProgress->Update((int)(work), (int)(mTotalWork));
|
|
return (updateResult == ProgressResult::Success);
|
|
}
|
|
bool set_smoothing_progress(int i) override {
|
|
iterations = i;
|
|
float work =
|
|
(is_audio[0] ? AUDIO_WORK_UNIT : MIDI_WORK_UNIT) * mFrames[0] +
|
|
(is_audio[1] ? AUDIO_WORK_UNIT : MIDI_WORK_UNIT) * mFrames[1] +
|
|
MATRIX_WORK_UNIT * mFrames[0] * mFrames[1];
|
|
work += i * wxMax(mFrames[0], mFrames[1]) * SMOOTHING_WORK_UNIT;
|
|
auto updateResult = mProgress->Update((int)(work), (int)(mTotalWork));
|
|
return (updateResult == ProgressResult::Success);
|
|
}
|
|
};
|
|
|
|
|
|
long mixer_process(void *mixer, float **buffer, long n)
|
|
{
|
|
Mixer *mix = (Mixer *) mixer;
|
|
long frame_count = mix->Process(std::max(0L, n));
|
|
*buffer = (float *) mix->GetBuffer();
|
|
return frame_count;
|
|
}
|
|
|
|
void MenuCommandHandler::OnScoreAlign(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tracks = project.GetTracks();
|
|
const auto rate = project.GetRate();
|
|
|
|
int numWaveTracksSelected = 0;
|
|
int numNoteTracksSelected = 0;
|
|
int numOtherTracksSelected = 0;
|
|
double endTime = 0.0;
|
|
|
|
// Iterate through once to make sure that there is exactly
|
|
// one WaveTrack and one NoteTrack selected.
|
|
GetTracks()->Selected().Visit(
|
|
[&](WaveTrack *wt) {
|
|
numWaveTracksSelected++;
|
|
endTime = endTime > wt->GetEndTime() ? endTime : wt->GetEndTime();
|
|
},
|
|
[&](NoteTrack *) {
|
|
numNoteTracksSelected++;
|
|
},
|
|
[&](Track*) {
|
|
numOtherTracksSelected++;
|
|
}
|
|
);
|
|
|
|
if(numWaveTracksSelected == 0 ||
|
|
numNoteTracksSelected != 1 ||
|
|
numOtherTracksSelected != 0){
|
|
AudacityMessageBox(wxString::Format(wxT("Please select at least one audio track and one MIDI track.")));
|
|
return;
|
|
}
|
|
|
|
// Creating the dialog also stores dialog into gScoreAlignDialog so
|
|
// that it can be delted by CloseScoreAlignDialog() either here or
|
|
// if the program is quit by the user while the dialog is up.
|
|
ScoreAlignParams params;
|
|
|
|
// safe because the class maintains a global resource pointer
|
|
safenew ScoreAlignDialog(params);
|
|
|
|
CloseScoreAlignDialog();
|
|
|
|
if (params.mStatus != wxID_OK) return;
|
|
|
|
// We're going to do it.
|
|
//pushing the state before the change is wrong (I think)
|
|
//PushState(_("Sync MIDI with Audio"), _("Sync MIDI with Audio"));
|
|
// Make a copy of the note track in case alignment is canceled or fails
|
|
auto holder = nt->Duplicate();
|
|
auto alignedNoteTrack = static_cast<NoteTrack*>(holder.get());
|
|
// Remove offset from NoteTrack because audio is
|
|
// mixed starting at zero and incorporating clip offsets.
|
|
if (alignedNoteTrack->GetOffset() < 0) {
|
|
// remove the negative offset data before alignment
|
|
nt->Clear(alignedNoteTrack->GetOffset(), 0);
|
|
} else if (alignedNoteTrack->GetOffset() > 0) {
|
|
alignedNoteTrack->Shift(alignedNoteTrack->GetOffset());
|
|
}
|
|
alignedNoteTrack->SetOffset(0);
|
|
|
|
WaveTrackConstArray waveTracks =
|
|
tracks->GetWaveTrackConstArray(true /* selectionOnly */);
|
|
|
|
int result;
|
|
{
|
|
Mixer mix(
|
|
waveTracks, // const WaveTrackConstArray &inputTracks
|
|
false, // mayThrow -- is this right?
|
|
Mixer::WarpOptions{ tracks->GetTimeTrack() }, // const WarpOptions &warpOptions
|
|
0.0, // double startTime
|
|
endTime, // double stopTime
|
|
2, // int numOutChannels
|
|
44100u, // size_t outBufferSize
|
|
true, // bool outInterleaved
|
|
rate, // double outRate
|
|
floatSample, // sampleFormat outFormat
|
|
true, // bool highQuality = true
|
|
NULL); // MixerSpec *mixerSpec = NULL
|
|
|
|
ASAProgress progress;
|
|
|
|
// There's a lot of adjusting made to incorporate the note track offset into
|
|
// the note track while preserving the position of notes within beats and
|
|
// measures. For debugging, you can see just the pre-scorealign note track
|
|
// manipulation by setting SKIP_ACTUAL_SCORE_ALIGNMENT. You could then, for
|
|
// example, save the modified note track in ".gro" form to read the details.
|
|
//#define SKIP_ACTUAL_SCORE_ALIGNMENT 1
|
|
#ifndef SKIP_ACTUAL_SCORE_ALIGNMENT
|
|
result = scorealign((void *) &mix, &mixer_process,
|
|
2 /* channels */, 44100.0 /* srate */, endTime,
|
|
&alignedNoteTrack->GetSeq(), &progress, params);
|
|
#else
|
|
result = SA_SUCCESS;
|
|
#endif
|
|
}
|
|
|
|
if (result == SA_SUCCESS) {
|
|
tracks->Replace(nt, std::move(holder));
|
|
project.RedrawProject();
|
|
AudacityMessageBox(wxString::Format(
|
|
_("Alignment completed: MIDI from %.2f to %.2f secs, Audio from %.2f to %.2f secs."),
|
|
params.mMidiStart, params.mMidiEnd,
|
|
params.mAudioStart, params.mAudioEnd));
|
|
project.PushState(_("Sync MIDI with Audio"), _("Sync MIDI with Audio"));
|
|
} else if (result == SA_TOOSHORT) {
|
|
AudacityMessageBox(wxString::Format(
|
|
_("Alignment error: input too short: MIDI from %.2f to %.2f secs, Audio from %.2f to %.2f secs."),
|
|
params.mMidiStart, params.mMidiEnd,
|
|
params.mAudioStart, params.mAudioEnd));
|
|
} else if (result == SA_CANCEL) {
|
|
// wrong way to recover...
|
|
//GetActiveProject()->OnUndo(); // recover any changes to note track
|
|
return; // no message when user cancels alignment
|
|
} else {
|
|
//GetActiveProject()->OnUndo(); // recover any changes to note track
|
|
AudacityMessageBox(_("Internal error reported by alignment process."));
|
|
}
|
|
}
|
|
#endif /* EXPERIMENTAL_SCOREALIGN */
|
|
|
|
|
|
void MenuCommandHandler::OnNewWaveTrack(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tracks = project.GetTracks();
|
|
auto trackFactory = project.GetTrackFactory();
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto defaultFormat = project.GetDefaultFormat();
|
|
auto rate = project.GetRate();
|
|
|
|
auto t = tracks->Add(trackFactory->NewWaveTrack(defaultFormat, rate));
|
|
project.SelectNone();
|
|
|
|
t->SetSelected(true);
|
|
|
|
project.PushState(_("Created new audio track"), _("New Track"));
|
|
|
|
project.RedrawProject();
|
|
trackPanel->EnsureVisible(t);
|
|
}
|
|
|
|
void MenuCommandHandler::OnNewStereoTrack(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tracks = project.GetTracks();
|
|
auto trackFactory = project.GetTrackFactory();
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto defaultFormat = project.GetDefaultFormat();
|
|
auto rate = project.GetRate();
|
|
|
|
project.SelectNone();
|
|
|
|
auto left = tracks->Add(trackFactory->NewWaveTrack(defaultFormat, rate));
|
|
left->SetSelected(true);
|
|
|
|
auto right = tracks->Add(trackFactory->NewWaveTrack(defaultFormat, rate));
|
|
right->SetSelected(true);
|
|
|
|
tracks->GroupChannels(*left, 2);
|
|
|
|
project.PushState(_("Created new stereo audio track"), _("New Track"));
|
|
|
|
project.RedrawProject();
|
|
trackPanel->EnsureVisible(left);
|
|
}
|
|
|
|
void MenuCommandHandler::OnNewLabelTrack(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tracks = project.GetTracks();
|
|
auto trackFactory = project.GetTrackFactory();
|
|
auto trackPanel = project.GetTrackPanel();
|
|
|
|
auto t = tracks->Add(trackFactory->NewLabelTrack());
|
|
|
|
project.SelectNone();
|
|
|
|
t->SetSelected(true);
|
|
|
|
project.PushState(_("Created new label track"), _("New Track"));
|
|
|
|
project.RedrawProject();
|
|
trackPanel->EnsureVisible(t);
|
|
}
|
|
|
|
void MenuCommandHandler::OnNewTimeTrack(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tracks = project.GetTracks();
|
|
auto trackFactory = project.GetTrackFactory();
|
|
auto trackPanel = project.GetTrackPanel();
|
|
|
|
if (tracks->GetTimeTrack()) {
|
|
AudacityMessageBox(_("This version of Audacity only allows one time track for each project window."));
|
|
return;
|
|
}
|
|
|
|
auto t = tracks->AddToHead(trackFactory->NewTimeTrack());
|
|
|
|
project.SelectNone();
|
|
|
|
t->SetSelected(true);
|
|
|
|
project.PushState(_("Created new time track"), _("New Track"));
|
|
|
|
project.RedrawProject();
|
|
trackPanel->EnsureVisible(t);
|
|
}
|
|
|
|
void MenuCommandHandler::OnMoveSelectionWithTracks(const CommandContext &WXUNUSED(context) )
|
|
{
|
|
bool bMoveWith;
|
|
gPrefs->Read(wxT("/GUI/MoveSelectionWithTracks"), &bMoveWith, false);
|
|
gPrefs->Write(wxT("/GUI/MoveSelectionWithTracks"), !bMoveWith);
|
|
gPrefs->Flush();
|
|
|
|
}
|
|
|
|
void MenuCommandHandler::OnSyncLock(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto trackPanel = project.GetTrackPanel();
|
|
|
|
bool bSyncLockTracks;
|
|
gPrefs->Read(wxT("/GUI/SyncLockTracks"), &bSyncLockTracks, false);
|
|
gPrefs->Write(wxT("/GUI/SyncLockTracks"), !bSyncLockTracks);
|
|
gPrefs->Flush();
|
|
|
|
// Toolbar, project sync-lock handled within
|
|
MenuManager::ModifyAllProjectToolbarMenus();
|
|
|
|
trackPanel->Refresh(false);
|
|
}
|
|
|
|
|
|
|
|
void MenuCommandHandler::OnRemoveTracks(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tracks = project.GetTracks();
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto mixerBoard = project.GetMixerBoard();
|
|
|
|
std::vector<Track*> toRemove;
|
|
for (auto track : tracks->Selected())
|
|
toRemove.push_back(track);
|
|
|
|
// Capture the track preceding the first removed track
|
|
Track *f{};
|
|
if (!toRemove.empty()) {
|
|
auto found = tracks->Find(toRemove[0]);
|
|
f = *--found;
|
|
}
|
|
|
|
if (mixerBoard)
|
|
for (auto track : tracks->Selected<PlayableTrack>())
|
|
mixerBoard->RemoveTrackCluster(track);
|
|
|
|
for (auto track : toRemove)
|
|
tracks->Remove(track);
|
|
|
|
if (!f)
|
|
// try to use the last track
|
|
f = *tracks->Any().rbegin();
|
|
if (f) {
|
|
// Try to use the first track after the removal
|
|
auto found = tracks->FindLeader(f);
|
|
auto t = *++found;
|
|
if (t)
|
|
f = t;
|
|
}
|
|
|
|
// If we actually have something left, then make sure it's seen
|
|
if (f)
|
|
trackPanel->EnsureVisible(f);
|
|
|
|
project.PushState(_("Removed audio track(s)"), _("Remove Track"));
|
|
|
|
trackPanel->UpdateViewIfNoTracks();
|
|
trackPanel->Refresh(false);
|
|
|
|
if (mixerBoard)
|
|
mixerBoard->Refresh(true);
|
|
}
|
|
|
|
//
|
|
// Help Menu
|
|
//
|
|
|
|
void MenuCommandHandler::OnAbout(const CommandContext &context)
|
|
{
|
|
#ifdef __WXMAC__
|
|
// Modeless dialog, consistent with other Mac applications
|
|
wxCommandEvent dummy;
|
|
wxGetApp().OnMenuAbout(dummy);
|
|
#else
|
|
auto &project = context.project;
|
|
|
|
// Windows and Linux still modal.
|
|
AboutDialog dlog(&project);
|
|
dlog.ShowModal();
|
|
#endif
|
|
}
|
|
|
|
void MenuCommandHandler::OnHelpWelcome(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
SplashDialog::Show2( &project );
|
|
}
|
|
|
|
void MenuCommandHandler::OnQuickHelp(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
HelpSystem::ShowHelp(
|
|
&project,
|
|
wxT("Quick_Help"));
|
|
}
|
|
|
|
|
|
void MenuCommandHandler::OnQuickFix(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
QuickFixDialog dlg( &project );
|
|
dlg.ShowModal();
|
|
}
|
|
|
|
void MenuCommandHandler::OnManual(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
HelpSystem::ShowHelp(
|
|
&project,
|
|
wxT("Main_Page"));
|
|
}
|
|
|
|
void MenuCommandHandler::OnCheckForUpdates(const CommandContext &WXUNUSED(context))
|
|
{
|
|
::OpenInDefaultBrowser( VerCheckUrl());
|
|
}
|
|
|
|
// Only does the update checks if it's an ALPHA build and not disabled by preferences.
|
|
void MenuCommandHandler::MayCheckForUpdates(AudacityProject &project)
|
|
{
|
|
#ifdef IS_ALPHA
|
|
OnCheckForUpdates(project);
|
|
#endif
|
|
}
|
|
|
|
void MenuCommandHandler::OnShowLog(const CommandContext &WXUNUSED(context) )
|
|
{
|
|
AudacityLogger *logger = wxGetApp().GetLogger();
|
|
if (logger) {
|
|
logger->Show();
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::OnBenchmark(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
::RunBenchmark(&project);
|
|
}
|
|
|
|
#if defined(EXPERIMENTAL_CRASH_REPORT)
|
|
void MenuCommandHandler::OnCrashReport(const CommandContext &WXUNUSED(context) )
|
|
{
|
|
// Change to "1" to test a real crash
|
|
#if 0
|
|
char *p = 0;
|
|
*p = 1234;
|
|
#endif
|
|
wxGetApp().GenerateCrashReport(wxDebugReport::Context_Current);
|
|
}
|
|
#endif
|
|
|
|
void MenuCommandHandler::OnSimulateRecordingErrors(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto commandManager = project.GetCommandManager();
|
|
|
|
bool &setting = gAudioIO->mSimulateRecordingErrors;
|
|
commandManager->Check(wxT("SimulateRecordingErrors"), !setting);
|
|
setting = !setting;
|
|
}
|
|
|
|
void MenuCommandHandler::OnDetectUpstreamDropouts(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto commandManager = project.GetCommandManager();
|
|
|
|
bool &setting = gAudioIO->mDetectUpstreamDropouts;
|
|
commandManager->Check(wxT("DetectUpstreamDropouts"), !setting);
|
|
setting = !setting;
|
|
}
|
|
|
|
void MenuCommandHandler::OnScreenshot(const CommandContext &WXUNUSED(context) )
|
|
{
|
|
::OpenScreenshotTools();
|
|
}
|
|
|
|
void MenuCommandHandler::OnAudioDeviceInfo(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
|
|
wxString info = gAudioIO->GetDeviceInfo();
|
|
|
|
wxDialogWrapper dlg(&project, wxID_ANY, wxString(_("Audio Device Info")));
|
|
dlg.SetName(dlg.GetTitle());
|
|
ShuttleGui S(&dlg, eIsCreating);
|
|
|
|
wxTextCtrl *text;
|
|
S.StartVerticalLay();
|
|
{
|
|
S.SetStyle(wxTE_MULTILINE | wxTE_READONLY);
|
|
text = S.Id(wxID_STATIC).AddTextWindow(info);
|
|
S.AddStandardButtons(eOkButton | eCancelButton);
|
|
}
|
|
S.EndVerticalLay();
|
|
|
|
dlg.FindWindowById(wxID_OK)->SetLabel(_("&Save"));
|
|
dlg.SetSize(350, 450);
|
|
|
|
if (dlg.ShowModal() == wxID_OK)
|
|
{
|
|
wxString fName = FileNames::SelectFile(FileNames::Operation::Export,
|
|
_("Save Device Info"),
|
|
wxEmptyString,
|
|
wxT("deviceinfo.txt"),
|
|
wxT("txt"),
|
|
wxT("*.txt"),
|
|
wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxRESIZE_BORDER,
|
|
&project);
|
|
if (!fName.IsEmpty())
|
|
{
|
|
if (!text->SaveFile(fName))
|
|
{
|
|
AudacityMessageBox(_("Unable to save device info"), _("Save Device Info"));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
void MenuCommandHandler::OnMidiDeviceInfo(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
|
|
wxString info = gAudioIO->GetMidiDeviceInfo();
|
|
|
|
wxDialogWrapper dlg(&project, wxID_ANY, wxString(_("MIDI Device Info")));
|
|
dlg.SetName(dlg.GetTitle());
|
|
ShuttleGui S(&dlg, eIsCreating);
|
|
|
|
wxTextCtrl *text;
|
|
S.StartVerticalLay();
|
|
{
|
|
S.SetStyle(wxTE_MULTILINE | wxTE_READONLY);
|
|
text = S.Id(wxID_STATIC).AddTextWindow(info);
|
|
S.AddStandardButtons(eOkButton | eCancelButton);
|
|
}
|
|
S.EndVerticalLay();
|
|
|
|
dlg.FindWindowById(wxID_OK)->SetLabel(_("&Save"));
|
|
dlg.SetSize(350, 450);
|
|
|
|
if (dlg.ShowModal() == wxID_OK)
|
|
{
|
|
wxString fName = FileNames::SelectFile(FileNames::Operation::Export,
|
|
_("Save MIDI Device Info"),
|
|
wxEmptyString,
|
|
wxT("midideviceinfo.txt"),
|
|
wxT("txt"),
|
|
wxT("*.txt"),
|
|
wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxRESIZE_BORDER,
|
|
&project);
|
|
if (!fName.IsEmpty())
|
|
{
|
|
if (!text->SaveFile(fName))
|
|
{
|
|
AudacityMessageBox(_("Unable to save MIDI device info"), _("Save MIDI Device Info"));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void MenuCommandHandler::DoPanTracks(AudacityProject &project, float PanValue)
|
|
{
|
|
auto tracks = project.GetTracks();
|
|
auto mixerBoard = project.GetMixerBoard();
|
|
|
|
// count selected wave tracks
|
|
const auto range = tracks->Any< WaveTrack >();
|
|
const auto selectedRange = range + &Track::IsSelected;
|
|
auto count = selectedRange.size();
|
|
|
|
// iter through them, all if none selected.
|
|
for (auto left : count == 0 ? range : selectedRange )
|
|
left->SetPan( PanValue );
|
|
|
|
project.RedrawProject();
|
|
if (mixerBoard)
|
|
mixerBoard->UpdatePan();
|
|
|
|
auto flags = UndoPush::AUTOSAVE;
|
|
/*i18n-hint: One or more audio tracks have been panned*/
|
|
project.PushState(_("Panned audio track(s)"), _("Pan Track"), flags);
|
|
flags = flags | UndoPush::CONSOLIDATE;
|
|
}
|
|
|
|
void MenuCommandHandler::OnPanLeft(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
DoPanTracks( project, -1.0);
|
|
}
|
|
|
|
void MenuCommandHandler::OnPanRight(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
DoPanTracks( project, 1.0);
|
|
}
|
|
|
|
void MenuCommandHandler::OnPanCenter(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
DoPanTracks( project, 0.0);
|
|
}
|
|
|
|
void MenuCommandHandler::OnMuteAllTracks(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tracks = project.GetTracks();
|
|
auto soloSimple = project.IsSoloSimple();
|
|
auto soloNone = project.IsSoloNone();
|
|
auto mixerBoard = project.GetMixerBoard();
|
|
|
|
for (auto pt : tracks->Any<PlayableTrack>())
|
|
{
|
|
pt->SetMute(true);
|
|
if (soloSimple || soloNone)
|
|
pt->SetSolo(false);
|
|
}
|
|
|
|
project.ModifyState(true);
|
|
project.RedrawProject();
|
|
if (mixerBoard) {
|
|
mixerBoard->UpdateMute();
|
|
if (soloSimple || soloNone)
|
|
mixerBoard->UpdateSolo();
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::OnUnmuteAllTracks(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tracks = project.GetTracks();
|
|
auto soloSimple = project.IsSoloSimple();
|
|
auto soloNone = project.IsSoloNone();
|
|
auto mixerBoard = project.GetMixerBoard();
|
|
|
|
for (auto pt : tracks->Any<PlayableTrack>())
|
|
{
|
|
pt->SetMute(false);
|
|
if (soloSimple || soloNone)
|
|
pt->SetSolo(false);
|
|
}
|
|
|
|
project.ModifyState(true);
|
|
project.RedrawProject();
|
|
if (mixerBoard) {
|
|
mixerBoard->UpdateMute();
|
|
if (soloSimple || soloNone)
|
|
mixerBoard->UpdateSolo();
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::OnResample(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto projectRate = project.GetRate();
|
|
auto tracks = project.GetTracks();
|
|
auto &undoManager = *project.GetUndoManager();
|
|
|
|
int newRate;
|
|
|
|
while (true)
|
|
{
|
|
wxDialogWrapper dlg(&project, wxID_ANY, wxString(_("Resample")));
|
|
dlg.SetName(dlg.GetTitle());
|
|
ShuttleGui S(&dlg, eIsCreating);
|
|
wxString rate;
|
|
wxArrayString rates;
|
|
wxComboBox *cb;
|
|
|
|
rate.Printf(wxT("%ld"), lrint(projectRate));
|
|
|
|
rates.Add(wxT("8000"));
|
|
rates.Add(wxT("11025"));
|
|
rates.Add(wxT("16000"));
|
|
rates.Add(wxT("22050"));
|
|
rates.Add(wxT("32000"));
|
|
rates.Add(wxT("44100"));
|
|
rates.Add(wxT("48000"));
|
|
rates.Add(wxT("88200"));
|
|
rates.Add(wxT("96000"));
|
|
rates.Add(wxT("176400"));
|
|
rates.Add(wxT("192000"));
|
|
rates.Add(wxT("352800"));
|
|
rates.Add(wxT("384000"));
|
|
|
|
S.StartVerticalLay(true);
|
|
{
|
|
S.AddSpace(-1, 15);
|
|
|
|
S.StartHorizontalLay(wxCENTER, false);
|
|
{
|
|
cb = S.AddCombo(_("New sample rate (Hz):"),
|
|
rate,
|
|
&rates);
|
|
}
|
|
S.EndHorizontalLay();
|
|
|
|
S.AddSpace(-1, 15);
|
|
|
|
S.AddStandardButtons();
|
|
}
|
|
S.EndVerticalLay();
|
|
|
|
dlg.Layout();
|
|
dlg.Fit();
|
|
dlg.Center();
|
|
|
|
if (dlg.ShowModal() != wxID_OK)
|
|
{
|
|
return; // user cancelled dialog
|
|
}
|
|
|
|
long lrate;
|
|
if (cb->GetValue().ToLong(&lrate) && lrate >= 1 && lrate <= 1000000)
|
|
{
|
|
newRate = (int)lrate;
|
|
break;
|
|
}
|
|
|
|
AudacityMessageBox(_("The entered value is invalid"), _("Error"),
|
|
wxICON_ERROR, &project);
|
|
}
|
|
|
|
int ndx = 0;
|
|
auto flags = UndoPush::AUTOSAVE;
|
|
for (auto wt : tracks->Selected< WaveTrack >())
|
|
{
|
|
wxString msg;
|
|
|
|
msg.Printf(_("Resampling track %d"), ++ndx);
|
|
|
|
ProgressDialog progress(_("Resample"), msg);
|
|
|
|
// The resampling of a track may be stopped by the user. This might
|
|
// leave a track with multiple clips in a partially resampled state.
|
|
// But the thrown exception will cause rollback in the application
|
|
// level handler.
|
|
|
|
wt->Resample(newRate, &progress);
|
|
|
|
// Each time a track is successfully, completely resampled,
|
|
// commit that to the undo stack. The second and later times,
|
|
// consolidate.
|
|
|
|
project.PushState(_("Resampled audio track(s)"), _("Resample Track"), flags);
|
|
flags = flags | UndoPush::CONSOLIDATE;
|
|
}
|
|
|
|
undoManager.StopConsolidating();
|
|
project.RedrawProject();
|
|
|
|
// Need to reset
|
|
project.FinishAutoScroll();
|
|
}
|
|
|
|
void MenuCommandHandler::OnFullScreen(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto commandManager = project.GetCommandManager();
|
|
|
|
bool bChecked = !project.wxTopLevelWindow::IsFullScreen();
|
|
project.wxTopLevelWindow::ShowFullScreen(bChecked);
|
|
commandManager->Check(wxT("FullScreenOnOff"), bChecked);
|
|
}
|