10108 lines
320 KiB
C++
10108 lines
320 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 <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"
|
|
#ifdef USE_MIDI
|
|
#include "import/ImportMIDI.h"
|
|
#endif // USE_MIDI
|
|
#include "import/ImportRaw.h"
|
|
#include "export/Export.h"
|
|
#include "export/ExportMultiple.h"
|
|
#include "prefs/PrefsDialog.h"
|
|
#include "prefs/PlaybackPrefs.h"
|
|
#include "ShuttleGui.h"
|
|
#include "HistoryWindow.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"
|
|
#include "Printing.h"
|
|
#ifdef USE_MIDI
|
|
#include "NoteTrack.h"
|
|
#endif // USE_MIDI
|
|
#include "Tags.h"
|
|
#include "TimeTrack.h"
|
|
#include "Mix.h"
|
|
#include "AboutDialog.h"
|
|
#include "Benchmark.h"
|
|
#include "Screenshot.h"
|
|
#include "ondemand/ODManager.h"
|
|
|
|
#include "BatchProcessDialog.h"
|
|
#include "BatchCommands.h"
|
|
#include "prefs/BatchPrefs.h"
|
|
|
|
#include "toolbars/ToolManager.h"
|
|
#include "toolbars/ControlToolBar.h"
|
|
#include "toolbars/ToolsToolBar.h"
|
|
#include "toolbars/EditToolBar.h"
|
|
#include "toolbars/DeviceToolBar.h"
|
|
#include "toolbars/MixerToolBar.h"
|
|
#include "toolbars/TranscriptionToolBar.h"
|
|
|
|
#include "tracks/ui/SelectHandle.h"
|
|
|
|
#include "widgets/LinkingHtmlWindow.h"
|
|
|
|
#include "Experimental.h"
|
|
#include "PlatformCompatibility.h"
|
|
#include "FileNames.h"
|
|
#include "TimeDialog.h"
|
|
#include "TimerRecordDialog.h"
|
|
#include "SoundActivatedRecord.h"
|
|
#include "LabelDialog.h"
|
|
|
|
#include "SplashDialog.h"
|
|
#include "widgets/HelpSystem.h"
|
|
#include "DeviceManager.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 "tracks/ui/Scrubbing.h"
|
|
#include "prefs/TracksPrefs.h"
|
|
|
|
#include "widgets/Meter.h"
|
|
#include "widgets/ErrorDialog.h"
|
|
#include "./commands/AudacityCommand.h"
|
|
#include "commands/CommandContext.h"
|
|
|
|
MenuCommandHandler &GetMenuCommandHandler(AudacityProject &project)
|
|
{ return *project.mMenuCommandHandler; }
|
|
|
|
MenuManager &GetMenuManager(AudacityProject &project)
|
|
{ return *project.mMenuManager; }
|
|
|
|
MenuCommandHandler::MenuCommandHandler()
|
|
{
|
|
//Initialize the last selection adjustment time.
|
|
mLastSelectionAdjustment = ::wxGetLocalTimeMillis();
|
|
}
|
|
|
|
MenuCommandHandler::~MenuCommandHandler()
|
|
{
|
|
}
|
|
|
|
MenuCreator::MenuCreator(){
|
|
}
|
|
|
|
MenuCreator::~MenuCreator()
|
|
{
|
|
if (wxGetApp().GetRecentFiles())
|
|
{
|
|
wxGetApp().GetRecentFiles()->RemoveMenu(mRecentFilesMenu);
|
|
}
|
|
}
|
|
|
|
enum {
|
|
kAlignStartZero = 0,
|
|
kAlignStartSelStart,
|
|
kAlignStartSelEnd,
|
|
kAlignEndSelStart,
|
|
kAlignEndSelEnd,
|
|
// The next two are only in one subMenu, so more easily handled at the end.
|
|
kAlignEndToEnd,
|
|
kAlignTogether
|
|
};
|
|
|
|
// Post Timer Recording Actions
|
|
// Ensure this matches the enum in TimerRecordDialog.cpp
|
|
enum {
|
|
POST_TIMER_RECORD_STOPPED = -3,
|
|
POST_TIMER_RECORD_CANCEL_WAIT,
|
|
POST_TIMER_RECORD_CANCEL,
|
|
POST_TIMER_RECORD_NOTHING,
|
|
POST_TIMER_RECORD_CLOSE,
|
|
POST_TIMER_RECORD_RESTART,
|
|
POST_TIMER_RECORD_SHUTDOWN
|
|
};
|
|
|
|
#include "commands/CommandContext.h"
|
|
#include "commands/ScreenshotCommand.h"
|
|
|
|
#include "BatchCommands.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);
|
|
gPrefs->Read(wxT("/AudioIO/SeekShortPeriod"), &mSeekShort, 1.0);
|
|
gPrefs->Read(wxT("/AudioIO/SeekLongPeriod"), &mSeekLong, 15.0);
|
|
}
|
|
|
|
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.
|
|
}
|
|
|
|
/// 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("...")
|
|
|
|
void MenuCreator::CreateMenusAndCommands(AudacityProject &project)
|
|
{
|
|
using Options = CommandManager::Options;
|
|
const Options checkOff = Options{}.CheckState( false );
|
|
const Options checkOn = Options{}.CheckState( true );
|
|
|
|
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);
|
|
c->SetOccultCommands( false );
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// File menu
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
c->BeginMenu(_("&File"));
|
|
|
|
/*i18n-hint: "New" is an action (verb) to create a NEW project*/
|
|
c->AddItem( wxT("New"), XXO("&New"), FN(OnNew),
|
|
AudioIONotBusyFlag, wxT("Ctrl+N") );
|
|
|
|
/*i18n-hint: (verb)*/
|
|
c->AddItem( wxT("Open"), XXO("&Open..."), FN(OnOpen),
|
|
AudioIONotBusyFlag, wxT("Ctrl+O") );
|
|
|
|
#ifdef EXPERIMENTAL_RESET
|
|
// Empty the current project and forget its name and path. DANGEROUS
|
|
// It's just for developers.
|
|
// Do not translate this menu item (no XXO).
|
|
// It MUST not be shown to regular users.
|
|
c->AddItem( wxT("Reset"), wxT("&Dangerous Reset..."), FN(OnProjectReset), wxT(""),
|
|
AudioIONotBusyFlag );
|
|
#endif
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
CreateRecentFilesMenu(c);
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
c->AddItem( wxT("Close"), XXO("&Close"), FN(OnClose),
|
|
AudioIONotBusyFlag, wxT("Ctrl+W") );
|
|
|
|
c->AddSeparator();
|
|
|
|
c->BeginSubMenu( _("&Save Project") );
|
|
c->AddItem( wxT("Save"), XXO("&Save Project"), FN(OnSave),
|
|
AudioIONotBusyFlag | UnsavedChangesFlag, wxT("Ctrl+S") );
|
|
c->AddItem( wxT("SaveAs"), XXO("Save Project &As..."), FN(OnSaveAs),
|
|
AudioIONotBusyFlag );
|
|
// TODO: The next two items should be disabled if project is empty
|
|
c->AddItem( wxT("SaveCopy"), XXO("Save Lossless Copy of Project..."),
|
|
FN(OnSaveCopy), AudioIONotBusyFlag );
|
|
#ifdef USE_LIBVORBIS
|
|
c->AddItem( wxT("SaveCompressed"), XXO("&Save Compressed Copy of Project..."),
|
|
FN(OnSaveCompressed), AudioIONotBusyFlag );
|
|
#endif
|
|
c->EndSubMenu();
|
|
c->AddSeparator();
|
|
|
|
c->BeginSubMenu( _("&Export") );
|
|
|
|
// Enable Export audio commands only when there are audio tracks.
|
|
c->AddItem( wxT("ExportMp3"), XXO("Export as MP&3"), FN(OnExportMp3),
|
|
AudioIONotBusyFlag | WaveTracksExistFlag );
|
|
|
|
c->AddItem( wxT("ExportWav"), XXO("Export as &WAV"), FN(OnExportWav),
|
|
AudioIONotBusyFlag | WaveTracksExistFlag );
|
|
|
|
c->AddItem( wxT("ExportOgg"), XXO("Export as &OGG"), FN(OnExportOgg),
|
|
AudioIONotBusyFlag | WaveTracksExistFlag );
|
|
|
|
c->AddItem( wxT("Export"), XXO("&Export Audio..."), FN(OnExportAudio),
|
|
AudioIONotBusyFlag | WaveTracksExistFlag, wxT("Ctrl+Shift+E") );
|
|
|
|
// Enable Export Selection commands only when there's a selection.
|
|
c->AddItem( wxT("ExportSel"), XXO("Expo&rt Selected Audio..."), FN(OnExportSelection),
|
|
AudioIONotBusyFlag | TimeSelectedFlag | WaveTracksSelectedFlag );
|
|
|
|
c->AddItem( wxT("ExportLabels"), XXO("Export &Labels..."), FN(OnExportLabels),
|
|
AudioIONotBusyFlag | LabelTracksExistFlag );
|
|
// Enable Export audio commands only when there are audio tracks.
|
|
c->AddItem( wxT("ExportMultiple"), XXO("Export &Multiple..."), FN(OnExportMultiple),
|
|
AudioIONotBusyFlag | WaveTracksExistFlag, wxT("Ctrl+Shift+L") );
|
|
#if defined(USE_MIDI)
|
|
c->AddItem( wxT("ExportMIDI"), XXO("Export MI&DI..."), FN(OnExportMIDI),
|
|
AudioIONotBusyFlag | NoteTracksExistFlag );
|
|
#endif
|
|
c->EndSubMenu();
|
|
|
|
c->BeginSubMenu(_("&Import"));
|
|
|
|
c->AddItem( wxT("ImportAudio"), XXO("&Audio..."), FN(OnImport),
|
|
AudioIONotBusyFlag, wxT("Ctrl+Shift+I") );
|
|
c->AddItem( wxT("ImportLabels"), XXO("&Labels..."), FN(OnImportLabels),
|
|
AudioIONotBusyFlag );
|
|
#ifdef USE_MIDI
|
|
c->AddItem( wxT("ImportMIDI"), XXO("&MIDI..."), FN(OnImportMIDI),
|
|
AudioIONotBusyFlag );
|
|
#endif // USE_MIDI
|
|
c->AddItem( wxT("ImportRaw"), XXO("&Raw Data..."), FN(OnImportRaw),
|
|
AudioIONotBusyFlag );
|
|
|
|
c->EndSubMenu();
|
|
c->AddSeparator();
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
c->AddItem( wxT("PageSetup"), XXO("Pa&ge Setup..."), FN(OnPageSetup),
|
|
AudioIONotBusyFlag | TracksExistFlag );
|
|
/* i18n-hint: (verb) It's item on a menu. */
|
|
c->AddItem( wxT("Print"), XXO("&Print..."), FN(OnPrint),
|
|
AudioIONotBusyFlag | TracksExistFlag );
|
|
|
|
c->AddSeparator();
|
|
|
|
// On the Mac, the Exit item doesn't actually go here...wxMac will pull it out
|
|
// and put it in the Audacity menu for us based on its ID.
|
|
/* i18n-hint: (verb) It's item on a menu. */
|
|
c->AddItem( wxT("Exit"), XXO("E&xit"), FN(OnExit),
|
|
AlwaysEnabledFlag, wxT("Ctrl+Q") );
|
|
|
|
c->EndMenu();
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Edit Menu
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
c->BeginMenu(_("&Edit"));
|
|
|
|
constexpr auto NotBusyTimeAndTracksFlags =
|
|
AudioIONotBusyFlag | TimeSelectedFlag | TracksSelectedFlag;
|
|
|
|
c->AddItem( wxT("Undo"), XXO("&Undo"), FN(OnUndo),
|
|
AudioIONotBusyFlag | UndoAvailableFlag, wxT("Ctrl+Z") );
|
|
|
|
// The default shortcut key for Redo is different on different platforms.
|
|
auto key =
|
|
#ifdef __WXMSW__
|
|
wxT("Ctrl+Y");
|
|
#else
|
|
wxT("Ctrl+Shift+Z");
|
|
#endif
|
|
|
|
c->AddItem( wxT("Redo"), XXO("&Redo"), FN(OnRedo),
|
|
AudioIONotBusyFlag | RedoAvailableFlag, key );
|
|
|
|
MenuManager::ModifyUndoMenuItems(project);
|
|
|
|
c->AddSeparator();
|
|
|
|
// Basic Edit coomands
|
|
/* i18n-hint: (verb)*/
|
|
c->AddItem( wxT("Cut"), XXO("Cu&t"), FN(OnCut),
|
|
AudioIONotBusyFlag | CutCopyAvailableFlag | NoAutoSelect,
|
|
Options{ wxT("Ctrl+X") }
|
|
.Mask( AudioIONotBusyFlag | CutCopyAvailableFlag ) );
|
|
c->AddItem( wxT("Delete"), XXO("&Delete"), FN(OnDelete),
|
|
AudioIONotBusyFlag | NoAutoSelect,
|
|
Options{ wxT("Ctrl+K") }
|
|
.Mask( AudioIONotBusyFlag ) );
|
|
/* i18n-hint: (verb)*/
|
|
c->AddItem( wxT("Copy"), XXO("&Copy"), FN(OnCopy),
|
|
AudioIONotBusyFlag | CutCopyAvailableFlag, wxT("Ctrl+C") );
|
|
/* i18n-hint: (verb)*/
|
|
c->AddItem( wxT("Paste"), XXO("&Paste"), FN(OnPaste),
|
|
AudioIONotBusyFlag, wxT("Ctrl+V") );
|
|
/* i18n-hint: (verb)*/
|
|
c->AddItem( wxT("Duplicate"), XXO("Duplic&ate"), FN(OnDuplicate),
|
|
NotBusyTimeAndTracksFlags, wxT("Ctrl+D") );
|
|
|
|
c->AddSeparator();
|
|
|
|
c->BeginSubMenu(_("R&emove Special"));
|
|
/* i18n-hint: (verb) Do a special kind of cut*/
|
|
c->AddItem( wxT("SplitCut"), XXO("Spl&it Cut"), FN(OnSplitCut),
|
|
NotBusyTimeAndTracksFlags, wxT("Ctrl+Alt+X") );
|
|
/* i18n-hint: (verb) Do a special kind of DELETE*/
|
|
c->AddItem( wxT("SplitDelete"), XXO("Split D&elete"), FN(OnSplitDelete),
|
|
NotBusyTimeAndTracksFlags, wxT("Ctrl+Alt+K") );
|
|
|
|
c->AddSeparator();
|
|
|
|
/* i18n-hint: (verb)*/
|
|
c->AddItem( wxT("Silence"), XXO("Silence Audi&o"), FN(OnSilence),
|
|
AudioIONotBusyFlag | TimeSelectedFlag | AudioTracksSelectedFlag, wxT("Ctrl+L") );
|
|
/* i18n-hint: (verb)*/
|
|
c->AddItem( wxT("Trim"), XXO("Tri&m Audio"), FN(OnTrim),
|
|
AudioIONotBusyFlag | TimeSelectedFlag | AudioTracksSelectedFlag, wxT("Ctrl+T") );
|
|
c->EndSubMenu();
|
|
|
|
c->AddSeparator();
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
c->BeginSubMenu(_("Clip B&oundaries"));
|
|
/* i18n-hint: (verb) It's an item on a menu. */
|
|
c->AddItem( wxT("Split"), XXO("Sp&lit"), FN(OnSplit),
|
|
AudioIONotBusyFlag | WaveTracksSelectedFlag, wxT("Ctrl+I") );
|
|
c->AddItem( wxT("SplitNew"), XXO("Split Ne&w"), FN(OnSplitNew),
|
|
AudioIONotBusyFlag | TimeSelectedFlag | WaveTracksSelectedFlag, wxT("Ctrl+Alt+I") );
|
|
c->AddSeparator();
|
|
/* i18n-hint: (verb)*/
|
|
c->AddItem( wxT("Join"), XXO("&Join"), FN(OnJoin),
|
|
NotBusyTimeAndTracksFlags, wxT("Ctrl+J") );
|
|
c->AddItem( wxT("Disjoin"), XXO("Detac&h at Silences"), FN(OnDisjoin),
|
|
NotBusyTimeAndTracksFlags, wxT("Ctrl+Alt+J") );
|
|
c->EndSubMenu();
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
c->BeginSubMenu(_("&Labels"));
|
|
|
|
c->AddItem( wxT("EditLabels"), XXO("&Edit Labels..."), FN(OnEditLabels),
|
|
AudioIONotBusyFlag );
|
|
|
|
c->AddSeparator();
|
|
|
|
c->AddItem( wxT("AddLabel"), XXO("Add Label at &Selection"), FN(OnAddLabel),
|
|
AlwaysEnabledFlag, wxT("Ctrl+B") );
|
|
c->AddItem( wxT("AddLabelPlaying"), XXO("Add Label at &Playback Position"),
|
|
FN(OnAddLabelPlaying),
|
|
AudioIOBusyFlag,
|
|
#ifdef __WXMAC__
|
|
wxT("Ctrl+.")
|
|
#else
|
|
wxT("Ctrl+M")
|
|
#endif
|
|
);
|
|
c->AddItem( wxT("PasteNewLabel"), XXO("Paste Te&xt to New Label"), FN(OnPasteNewLabel),
|
|
AudioIONotBusyFlag, wxT("Ctrl+Alt+V") );
|
|
|
|
c->AddSeparator();
|
|
|
|
c->AddItem( wxT("TypeToCreateLabel"), XXO("&Type to Create a Label (on/off)"),
|
|
FN(OnToggleTypeToCreateLabel), AlwaysEnabledFlag, checkOff );
|
|
|
|
c->EndSubMenu();
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
c->BeginSubMenu(_("La&beled Audio"));
|
|
|
|
constexpr auto NotBusyLabelsAndWaveFlags =
|
|
AudioIONotBusyFlag |
|
|
LabelsSelectedFlag | WaveTracksExistFlag | TimeSelectedFlag;
|
|
|
|
/* i18n-hint: (verb)*/
|
|
c->AddItem( wxT("CutLabels"), XXO("&Cut"), FN(OnCutLabels),
|
|
AudioIONotBusyFlag | LabelsSelectedFlag | WaveTracksExistFlag | TimeSelectedFlag | IsNotSyncLockedFlag,
|
|
Options{ wxT("Alt+X"), _("Label Cut") } );
|
|
c->AddItem( wxT("DeleteLabels"), XXO("&Delete"), FN(OnDeleteLabels),
|
|
AudioIONotBusyFlag | LabelsSelectedFlag | WaveTracksExistFlag | TimeSelectedFlag | IsNotSyncLockedFlag,
|
|
Options{ wxT("Alt+K"), _("Label Delete") } );
|
|
|
|
c->AddSeparator();
|
|
|
|
/* i18n-hint: (verb) A special way to cut out a piece of audio*/
|
|
c->AddItem( wxT("SplitCutLabels"), XXO("&Split Cut"), FN(OnSplitCutLabels),
|
|
NotBusyLabelsAndWaveFlags,
|
|
Options{ wxT("Alt+Shift+X"), _("Label Split Cut") } );
|
|
c->AddItem( wxT("SplitDeleteLabels"), XXO("Sp&lit Delete"),
|
|
FN(OnSplitDeleteLabels), NotBusyLabelsAndWaveFlags,
|
|
Options{ wxT("Alt+Shift+K"), _("Label Split Delete") } );
|
|
|
|
c->AddSeparator();
|
|
|
|
|
|
c->AddItem( wxT("SilenceLabels"), XXO("Silence &Audio"),
|
|
FN(OnSilenceLabels), NotBusyLabelsAndWaveFlags,
|
|
Options{ wxT("Alt+L"), _("Label Silence") } );
|
|
/* i18n-hint: (verb)*/
|
|
c->AddItem( wxT("CopyLabels"), XXO("Co&py"), FN(OnCopyLabels),
|
|
NotBusyLabelsAndWaveFlags,
|
|
Options{ wxT("Alt+Shift+C"), _("Label Copy") } );
|
|
|
|
c->AddSeparator();
|
|
|
|
/* i18n-hint: (verb)*/
|
|
c->AddItem( wxT("SplitLabels"), XXO("Spli&t"), FN(OnSplitLabels),
|
|
AudioIONotBusyFlag | LabelsSelectedFlag | WaveTracksExistFlag,
|
|
Options{ wxT("Alt+I"), _("Label Split") } );
|
|
/* i18n-hint: (verb)*/
|
|
c->AddItem( wxT("JoinLabels"), XXO("&Join"), FN(OnJoinLabels),
|
|
NotBusyLabelsAndWaveFlags,
|
|
Options{ wxT("Alt+J"), _("Label Join") } );
|
|
c->AddItem( wxT("DisjoinLabels"), XXO("Detac&h at Silences"),
|
|
FN(OnDisjoinLabels), NotBusyLabelsAndWaveFlags, wxT("Alt+Shift+J") );
|
|
|
|
c->EndSubMenu();
|
|
|
|
c->AddItem( wxT("EditMetaData"), XXO("Me&tadata..."), FN(OnEditMetadata),
|
|
AudioIONotBusyFlag );
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
#ifndef __WXMAC__
|
|
c->AddSeparator();
|
|
#endif
|
|
|
|
// The default shortcut key for Preferences is different on different platforms.
|
|
key =
|
|
#ifdef __WXMAC__
|
|
wxT("Ctrl+,");
|
|
#else
|
|
wxT("Ctrl+P");
|
|
#endif
|
|
|
|
c->AddItem( wxT("Preferences"), XXO("Pre&ferences..."), FN(OnPreferences),
|
|
AudioIONotBusyFlag, key );
|
|
|
|
c->EndMenu();
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Select Menu
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
/* i18n-hint: (verb) It's an item on a menu. */
|
|
c->BeginMenu(_("&Select"));
|
|
|
|
c->AddItem( wxT("SelectAll"), XXO("&All"), FN(OnSelectAll),
|
|
TracksExistFlag,
|
|
Options{ wxT("Ctrl+A"), _("Select All") } );
|
|
c->AddItem( wxT("SelectNone"), XXO("&None"), FN(OnSelectNone),
|
|
TracksExistFlag,
|
|
Options{ wxT("Ctrl+Shift+A"), _("Select None") } );
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
c->BeginSubMenu(_("&Tracks"));
|
|
c->AddItem( wxT("SelAllTracks"), XXO("In All &Tracks"), FN(OnSelectAllTracks),
|
|
TracksExistFlag,
|
|
wxT("Ctrl+Shift+K") );
|
|
|
|
#ifdef EXPERIMENTAL_SYNC_LOCK
|
|
c->AddItem( wxT("SelSyncLockTracks"), XXO("In All &Sync-Locked Tracks"),
|
|
FN(OnSelectSyncLockSel),
|
|
TracksSelectedFlag | IsSyncLockedFlag,
|
|
Options{ wxT("Ctrl+Shift+Y"), _("Select Sync-Locked") } );
|
|
#endif
|
|
|
|
c->EndSubMenu();
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
c->BeginSubMenu(_("R&egion"));
|
|
|
|
c->AddItem( wxT("SetLeftSelection"), XXO("&Left at Playback Position"),
|
|
FN(OnSetLeftSelection), TracksExistFlag,
|
|
Options{ wxT("["), _("Set Selection Left at Play Position") } );
|
|
c->AddItem( wxT("SetRightSelection"), XXO("&Right at Playback Position"),
|
|
FN(OnSetRightSelection), TracksExistFlag,
|
|
Options{ wxT("]"), _("Set Selection Right at Play Position") } );
|
|
c->AddItem( wxT("SelTrackStartToCursor"), XXO("Track &Start to Cursor"), FN(OnSelectStartCursor), AlwaysEnabledFlag,
|
|
Options{ wxT("Shift+J"), _("Select Track Start to Cursor") } );
|
|
c->AddItem( wxT("SelCursorToTrackEnd"), XXO("Cursor to Track &End"), FN(OnSelectCursorEnd), AlwaysEnabledFlag,
|
|
Options{ wxT("Shift+K"), _("Select Cursor to Track End") } );
|
|
c->AddItem( wxT("SelTrackStartToEnd"), XXO("Track Start to En&d"), FN(OnSelectTrackStartToEnd), AlwaysEnabledFlag,
|
|
Options{}.LongName( _("Select Track Start to End") ) );
|
|
c->AddSeparator();
|
|
// GA: Audacity had 'Store Re&gion' here previously. There is no one-step
|
|
// way to restore the 'Saved Cursor Position' in Select Menu, so arguably
|
|
// using the word 'Selection' to do duty for both saving the region or the
|
|
// cursor is better. But it does not belong in a 'Region' submenu.
|
|
c->AddItem( wxT("SelSave"), XXO("S&tore Selection"), FN(OnSelectionSave),
|
|
WaveTracksSelectedFlag );
|
|
// Audacity had 'Retrieve Regio&n' here previously.
|
|
c->AddItem( wxT("SelRestore"), XXO("Retrieve Selectio&n"), FN(OnSelectionRestore),
|
|
TracksExistFlag );
|
|
|
|
c->EndSubMenu();
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
|
|
c->BeginSubMenu(_("S&pectral"));
|
|
c->AddItem( wxT("ToggleSpectralSelection"),
|
|
XXO("To&ggle Spectral Selection"), FN(OnToggleSpectralSelection),
|
|
TracksExistFlag, wxT("Q") );
|
|
c->AddItem( wxT("NextHigherPeakFrequency"),
|
|
XXO("Next &Higher Peak Frequency"), FN(OnNextHigherPeakFrequency),
|
|
TracksExistFlag );
|
|
c->AddItem( wxT("NextLowerPeakFrequency"),
|
|
XXO("Next &Lower Peak Frequency"), FN(OnNextLowerPeakFrequency),
|
|
TracksExistFlag );
|
|
c->EndSubMenu();
|
|
#endif
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
c->BeginSubMenu(_("Clip B&oundaries"));
|
|
c->AddItem( wxT("SelPrevClipBoundaryToCursor"), XXO("Pre&vious Clip Boundary to Cursor"),
|
|
FN(OnSelectPrevClipBoundaryToCursor),
|
|
WaveTracksExistFlag );
|
|
c->AddItem( wxT("SelCursorToNextClipBoundary"), XXO("Cursor to Ne&xt Clip Boundary"),
|
|
FN(OnSelectCursorToNextClipBoundary),
|
|
WaveTracksExistFlag );
|
|
c->AddItem( wxT("SelPrevClip"), XXO("Previo&us Clip"), FN(OnSelectPrevClip),
|
|
WaveTracksExistFlag,
|
|
Options{ wxT("Alt+,"), _("Select Previous Clip") } );
|
|
c->AddItem( wxT("SelNextClip"), XXO("N&ext Clip"), FN(OnSelectNextClip),
|
|
WaveTracksExistFlag,
|
|
Options{ wxT("Alt+."), _("Select Next Clip") } );
|
|
|
|
c->EndSubMenu();
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
c->AddSeparator();
|
|
|
|
c->AddItem( wxT("SelCursorStoredCursor"), XXO("Cursor to Stored &Cursor Position"), FN(OnSelectCursorStoredCursor),
|
|
TracksExistFlag,
|
|
Options{}.LongName( _("Select Cursor to Stored") ) );
|
|
|
|
c->AddItem( wxT("StoreCursorPosition"), XXO("Store Cursor Pos&ition"), FN(OnCursorPositionStore),
|
|
WaveTracksExistFlag );
|
|
// Save cursor position is used in some selections.
|
|
// Maybe there should be a restore for it?
|
|
|
|
c->AddSeparator();
|
|
|
|
c->AddItem( wxT("ZeroCross"), XXO("At &Zero Crossings"),
|
|
FN(OnZeroCrossing), TracksSelectedFlag,
|
|
Options{ wxT("Z"), _("Select Zero Crossing") } );
|
|
|
|
c->EndMenu();
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// View Menu
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
c->BeginMenu(_("&View"));
|
|
c->BeginSubMenu(_("&Zoom"));
|
|
|
|
c->AddItem( wxT("ZoomIn"), XXO("Zoom &In"), FN(OnZoomIn),
|
|
ZoomInAvailableFlag, wxT("Ctrl+1") );
|
|
c->AddItem( wxT("ZoomNormal"), XXO("Zoom &Normal"), FN(OnZoomNormal),
|
|
TracksExistFlag, wxT("Ctrl+2") );
|
|
c->AddItem( wxT("ZoomOut"), XXO("Zoom &Out"), FN(OnZoomOut),
|
|
ZoomOutAvailableFlag, wxT("Ctrl+3") );
|
|
c->AddItem( wxT("ZoomSel"), XXO("&Zoom to Selection"), FN(OnZoomSel),
|
|
TimeSelectedFlag, wxT("Ctrl+E") );
|
|
c->AddItem( wxT("ZoomToggle"), XXO("Zoom &Toggle"), FN(OnZoomToggle),
|
|
TracksExistFlag, wxT("Shift+Z") );
|
|
c->EndSubMenu();
|
|
|
|
c->BeginSubMenu(_("T&rack Size"));
|
|
c->AddItem( wxT("FitInWindow"), XXO("&Fit to Width"), FN(OnZoomFit),
|
|
TracksExistFlag, wxT("Ctrl+F") );
|
|
c->AddItem( wxT("FitV"), XXO("Fit to &Height"), FN(OnZoomFitV),
|
|
TracksExistFlag, wxT("Ctrl+Shift+F") );
|
|
c->AddItem( wxT("CollapseAllTracks"), XXO("&Collapse All Tracks"),
|
|
FN(OnCollapseAllTracks), TracksExistFlag, wxT("Ctrl+Shift+C") );
|
|
c->AddItem( wxT("ExpandAllTracks"), XXO("E&xpand Collapsed Tracks"),
|
|
FN(OnExpandAllTracks), TracksExistFlag, wxT("Ctrl+Shift+X") );
|
|
c->EndSubMenu();
|
|
|
|
c->BeginSubMenu(_("Sk&ip to"));
|
|
c->AddItem( wxT("SkipSelStart"), XXO("Selection Sta&rt"), FN(OnGoSelStart),
|
|
TimeSelectedFlag,
|
|
Options{ wxT("Ctrl+["), _("Skip to Selection Start") } );
|
|
c->AddItem( wxT("SkipSelEnd"), XXO("Selection En&d"), FN(OnGoSelEnd),
|
|
TimeSelectedFlag,
|
|
Options{ wxT("Ctrl+]"), _("Skip to Selection End") } );
|
|
c->EndSubMenu();
|
|
|
|
c->AddSeparator();
|
|
|
|
// History window should be available either for UndoAvailableFlag or RedoAvailableFlag,
|
|
// but we can't make the AddItem flags and mask have both, because they'd both have to be true for the
|
|
// command to be enabled.
|
|
// If user has Undone the entire stack, RedoAvailableFlag is on but UndoAvailableFlag is off.
|
|
// If user has done things but not Undone anything, RedoAvailableFlag is off but UndoAvailableFlag is on.
|
|
// So in either of those cases, (AudioIONotBusyFlag | UndoAvailableFlag | RedoAvailableFlag) mask
|
|
// would fail.
|
|
// The only way to fix this in the current architecture is to hack in special cases for RedoAvailableFlag
|
|
// in AudacityProject::UpdateMenus() (ugly) and CommandManager::HandleCommandEntry() (*really* ugly --
|
|
// shouldn't know about particular command names and flags).
|
|
// Here's the hack that would be necessary in AudacityProject::UpdateMenus(), if somebody decides to do it:
|
|
// // Because EnableUsingFlags requires all the flag bits match the corresponding mask bits,
|
|
// // "UndoHistory" specifies only AudioIONotBusyFlag | UndoAvailableFlag, because that
|
|
// // covers the majority of cases where it should be enabled.
|
|
// // If history is not empty but we've Undone the whole stack, we also want to enable,
|
|
// // to show the Redo's on stack.
|
|
// // "UndoHistory" might already be enabled, but add this check for RedoAvailableFlag.
|
|
// if (flags & RedoAvailableFlag)
|
|
// mCommandManager.Enable(wxT("UndoHistory"), true);
|
|
// So for now, enable the command regardless of stack. It will just show empty sometimes.
|
|
// FOR REDESIGN, clearly there are some limitations with the flags/mask bitmaps.
|
|
|
|
/* i18n-hint: Clicking this menu item shows the various editing steps that have been taken.*/
|
|
c->AddItem( wxT("UndoHistory"), XXO("&History..."), FN(OnHistory),
|
|
AudioIONotBusyFlag );
|
|
|
|
c->AddItem( wxT("Karaoke"), XXO("&Karaoke..."), FN(OnKaraoke), LabelTracksExistFlag );
|
|
c->AddItem( wxT("MixerBoard"), XXO("&Mixer Board..."), FN(OnMixerBoard), PlayableTracksExistFlag );
|
|
|
|
c->AddSeparator();
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
c->BeginSubMenu(_("&Toolbars"));
|
|
|
|
/* i18n-hint: (verb)*/
|
|
c->AddItem( wxT("ResetToolbars"), XXO("Reset Toolb&ars"), FN(OnResetToolBars), AlwaysEnabledFlag );
|
|
c->AddSeparator();
|
|
|
|
/* i18n-hint: Clicking this menu item shows the toolbar with the big buttons on it (play record etc)*/
|
|
c->AddItem( wxT("ShowTransportTB"), XXO("&Transport Toolbar"), FN(OnShowTransportToolBar), AlwaysEnabledFlag, checkOff );
|
|
/* i18n-hint: Clicking this menu item shows a toolbar that has some tools in it*/
|
|
c->AddItem( wxT("ShowToolsTB"), XXO("T&ools Toolbar"), FN(OnShowToolsToolBar), AlwaysEnabledFlag, checkOff );
|
|
/* i18n-hint: Clicking this menu item shows the toolbar with the recording level meters*/
|
|
c->AddItem( wxT("ShowRecordMeterTB"), XXO("&Recording Meter Toolbar"), FN(OnShowRecordMeterToolBar), AlwaysEnabledFlag, checkOff );
|
|
/* i18n-hint: Clicking this menu item shows the toolbar with the playback level meter*/
|
|
c->AddItem( wxT("ShowPlayMeterTB"), XXO("&Playback Meter Toolbar"), FN(OnShowPlayMeterToolBar), AlwaysEnabledFlag, checkOff );
|
|
|
|
/* --i18nhint: Clicking this menu item shows the toolbar which has sound level meters*/
|
|
//c->AddItem( wxT("ShowMeterTB"), XXO("Co&mbined Meter Toolbar"), FN(OnShowMeterToolBar), AlwaysEnabledFlag, checkOff );
|
|
|
|
/* i18n-hint: Clicking this menu item shows the toolbar with the mixer*/
|
|
c->AddItem( wxT("ShowMixerTB"), XXO("Mi&xer Toolbar"), FN(OnShowMixerToolBar), AlwaysEnabledFlag, checkOff );
|
|
/* i18n-hint: Clicking this menu item shows the toolbar for editing*/
|
|
c->AddItem( wxT("ShowEditTB"), XXO("&Edit Toolbar"), FN(OnShowEditToolBar), AlwaysEnabledFlag, checkOff );
|
|
/* i18n-hint: Clicking this menu item shows the toolbar for transcription (currently just vary play speed)*/
|
|
c->AddItem( wxT("ShowTranscriptionTB"), XXO("Pla&y-at-Speed Toolbar"), FN(OnShowTranscriptionToolBar), AlwaysEnabledFlag, checkOff );
|
|
/* i18n-hint: Clicking this menu item shows the toolbar that enables Scrub or Seek playback and Scrub Ruler*/
|
|
c->AddItem( wxT("ShowScrubbingTB"), XXO("Scru&b Toolbar"), FN(OnShowScrubbingToolBar), AlwaysEnabledFlag, checkOff );
|
|
/* i18n-hint: Clicking this menu item shows the toolbar that manages devices*/
|
|
c->AddItem( wxT("ShowDeviceTB"), XXO("&Device Toolbar"), FN(OnShowDeviceToolBar), AlwaysEnabledFlag, checkOff );
|
|
/* i18n-hint: Clicking this menu item shows the toolbar for selecting a time range of audio*/
|
|
c->AddItem( wxT("ShowSelectionTB"), XXO("&Selection Toolbar"), FN(OnShowSelectionToolBar), AlwaysEnabledFlag, checkOff );
|
|
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
|
|
/* i18n-hint: Clicking this menu item shows the toolbar for selecting a frequency range of audio*/
|
|
c->AddItem( wxT("ShowSpectralSelectionTB"), XXO("Spe&ctral Selection Toolbar"), FN(OnShowSpectralSelectionToolBar), AlwaysEnabledFlag, checkOff );
|
|
#endif
|
|
|
|
c->EndSubMenu();
|
|
|
|
c->AddSeparator();
|
|
|
|
c->AddItem( wxT("ShowExtraMenus"), XXO("&Extra Menus (on/off)"), FN(OnShowExtraMenus),
|
|
AlwaysEnabledFlag,
|
|
Options{}.CheckState( gPrefs->Read(wxT("/GUI/ShowExtraMenus"), 0L) ) );
|
|
c->AddItem( wxT("ShowClipping"), XXO("&Show Clipping (on/off)"), FN(OnShowClipping),
|
|
AlwaysEnabledFlag,
|
|
Options{}.CheckState( gPrefs->Read(wxT("/GUI/ShowClipping"), 0L) ) );
|
|
#if defined(EXPERIMENTAL_EFFECTS_RACK)
|
|
c->AddItem( wxT("ShowEffectsRack"), XXO("Show Effects Rack"), FN(OnShowEffectsRack), AlwaysEnabledFlag, checkOff );
|
|
#endif
|
|
|
|
|
|
c->EndMenu();
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Transport Menu
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
/*i18n-hint: 'Transport' is the name given to the set of controls that
|
|
play, record, pause etc. */
|
|
c->BeginMenu(_("Tra&nsport"));
|
|
c->BeginSubMenu(_("Pl&aying"));
|
|
/* i18n-hint: (verb) Start or Stop audio playback*/
|
|
c->AddItem( wxT("PlayStop"), XXO("Pl&ay/Stop"), FN(OnPlayStop),
|
|
CanStopAudioStreamFlag, wxT("Space") );
|
|
c->AddItem( wxT("PlayStopSelect"), XXO("Play/Stop and &Set Cursor"),
|
|
FN(OnPlayStopSelect), CanStopAudioStreamFlag, wxT("X") );
|
|
c->AddItem( wxT("PlayLooped"), XXO("&Loop Play"), FN(OnPlayLooped),
|
|
CanStopAudioStreamFlag, wxT("Shift+Space") );
|
|
c->AddItem( wxT("Pause"), XXO("&Pause"), FN(OnPause),
|
|
CanStopAudioStreamFlag, wxT("P") );
|
|
c->EndSubMenu();
|
|
|
|
c->BeginSubMenu( _("&Recording"));
|
|
constexpr auto CanStopFlags = AudioIONotBusyFlag | CanStopAudioStreamFlag;
|
|
/* i18n-hint: (verb)*/
|
|
c->AddItem( wxT("Record1stChoice"), XXO("&Record"), FN(OnRecord),
|
|
CanStopFlags, wxT("R") );
|
|
// The OnRecord2ndChoice function is: if normal record records beside,
|
|
// it records below, if normal record records below, it records beside.
|
|
// TODO: Do 'the right thing' with other options like TimerRecord.
|
|
bool bPreferNewTrack;
|
|
gPrefs->Read("/GUI/PreferNewTrackRecord",&bPreferNewTrack, false);
|
|
c->AddItem( wxT("Record2ndChoice"),
|
|
// Our first choice is bound to R (by default) and gets the prime position.
|
|
// We supply the name for the 'other one' here. It should be bound to Shift+R
|
|
(bPreferNewTrack ? _("&Append Record") : _("Record &New Track")),
|
|
false, FN(OnRecord2ndChoice), CanStopFlags,
|
|
wxT("Shift+R")
|
|
);
|
|
|
|
c->AddItem( wxT("TimerRecord"), XXO("&Timer Record..."),
|
|
FN(OnTimerRecord), CanStopFlags, wxT("Shift+T") );
|
|
|
|
#ifdef EXPERIMENTAL_PUNCH_AND_ROLL
|
|
c->AddItem( wxT("PunchAndRoll"), XXO("Punch and Rol&l Record"), FN(OnPunchAndRoll),
|
|
WaveTracksExistFlag | AudioIONotBusyFlag, wxT("Shift+D") );
|
|
#endif
|
|
|
|
// JKC: I decided to duplicate this between play and record, rather than put it
|
|
// at the top level. AddItem can now cope with simple duplicated items.
|
|
// PRL: This second registration of wxT("Pause"), with unspecified flags,
|
|
// in fact will use the same flags as in the previous registration.
|
|
c->AddItem( wxT("Pause"), XXO("&Pause"), FN(OnPause), CanStopFlags,
|
|
wxT("P") );
|
|
c->EndSubMenu();
|
|
|
|
// Scrubbing sub-menu
|
|
project.GetScrubber().AddMenuItems();
|
|
|
|
// JKC: ANSWER-ME: How is 'cursor to' different to 'Skip To' and how is it useful?
|
|
// GA: 'Skip to' moves the viewpoint to center of the track and preserves the
|
|
// selection. 'Cursor to' does neither. 'Center at' might describe it better than 'Skip'.
|
|
c->BeginSubMenu(_("&Cursor to"));
|
|
|
|
c->AddItem( wxT("CursSelStart"), XXO("Selection Star&t"), FN(OnCursorSelStart),
|
|
TimeSelectedFlag,
|
|
Options{}.LongName( _("Cursor to Selection Start") ) );
|
|
c->AddItem( wxT("CursSelEnd"), XXO("Selection En&d"), FN(OnCursorSelEnd),
|
|
TimeSelectedFlag,
|
|
Options{}.LongName( _("Cursor to Selection End") ) );
|
|
|
|
c->AddItem( wxT("CursTrackStart"), XXO("Track &Start"), FN(OnCursorTrackStart),
|
|
TracksSelectedFlag,
|
|
Options{ wxT("J"), _("Cursor to Track Start") } );
|
|
c->AddItem( wxT("CursTrackEnd"), XXO("Track &End"), FN(OnCursorTrackEnd),
|
|
TracksSelectedFlag,
|
|
Options{ wxT("K"), _("Cursor to Track End") } );
|
|
|
|
c->AddItem( wxT("CursPrevClipBoundary"), XXO("Pre&vious Clip Boundary"), FN(OnCursorPrevClipBoundary),
|
|
WaveTracksExistFlag,
|
|
Options{}.LongName( _("Cursor to Prev Clip Boundary") ) );
|
|
c->AddItem( wxT("CursNextClipBoundary"), XXO("Ne&xt Clip Boundary"), FN(OnCursorNextClipBoundary),
|
|
WaveTracksExistFlag,
|
|
Options{}.LongName( _("Cursor to Next Clip Boundary") ) );
|
|
|
|
c->AddItem( wxT("CursProjectStart"), XXO("&Project Start"),
|
|
FN(OnSkipStart), CanStopFlags,
|
|
Options{ wxT("Home"), _("Cursor to Project Start") } );
|
|
c->AddItem( wxT("CursProjectEnd"), XXO("Project E&nd"), FN(OnSkipEnd),
|
|
CanStopFlags,
|
|
Options{ wxT("End"), _("Cursor to Project End") } );
|
|
|
|
c->EndSubMenu();
|
|
|
|
c->AddSeparator();
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
c->BeginSubMenu(_("Pla&y Region"));
|
|
|
|
c->AddItem( wxT("LockPlayRegion"), XXO("&Lock"), FN(OnLockPlayRegion),
|
|
PlayRegionNotLockedFlag );
|
|
c->AddItem( wxT("UnlockPlayRegion"), XXO("&Unlock"), FN(OnUnlockPlayRegion),
|
|
PlayRegionLockedFlag );
|
|
|
|
c->EndSubMenu();
|
|
|
|
c->AddSeparator();
|
|
|
|
c->AddItem( wxT("RescanDevices"), XXO("R&escan Audio Devices"), FN(OnRescanDevices),
|
|
AudioIONotBusyFlag | CanStopAudioStreamFlag );
|
|
|
|
c->BeginSubMenu(_("Transport &Options"));
|
|
// Sound Activated recording options
|
|
c->AddItem( wxT("SoundActivationLevel"), XXO("Sound Activation Le&vel..."), FN(OnSoundActivated),
|
|
AudioIONotBusyFlag | CanStopAudioStreamFlag );
|
|
c->AddItem( wxT("SoundActivation"), XXO("Sound A&ctivated Recording (on/off)"), FN(OnToggleSoundActivated),
|
|
AudioIONotBusyFlag | CanStopAudioStreamFlag, checkOff );
|
|
c->AddSeparator();
|
|
|
|
c->AddItem( wxT("PinnedHead"), XXO("Pinned Play/Record &Head (on/off)"),
|
|
FN(OnTogglePinnedHead),
|
|
// Switching of scrolling on and off is permitted even during transport
|
|
AlwaysEnabledFlag, checkOff );
|
|
|
|
c->AddItem( wxT("Overdub"), XXO("&Overdub (on/off)"), FN(OnTogglePlayRecording),
|
|
AudioIONotBusyFlag | CanStopAudioStreamFlag, checkOn );
|
|
c->AddItem( wxT("SWPlaythrough"), XXO("So&ftware Playthrough (on/off)"), FN(OnToggleSWPlaythrough),
|
|
AudioIONotBusyFlag | CanStopAudioStreamFlag, checkOff );
|
|
|
|
|
|
#ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT
|
|
c->AddItem( wxT("AutomatedInputLevelAdjustmentOnOff"), XXO("A&utomated Recording Level Adjustment (on/off)"), FN(OnToggleAutomatedInputLevelAdjustment),
|
|
AudioIONotBusyFlag | CanStopAudioStreamFlag, checkOff );
|
|
#endif
|
|
c->EndSubMenu();
|
|
|
|
c->EndMenu();
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Tracks Menu (formerly Project Menu)
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
c->BeginMenu(_("&Tracks"));
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
c->BeginSubMenu(_("Add &New"));
|
|
|
|
c->AddItem( wxT("NewMonoTrack"), XXO("&Mono Track"), FN(OnNewWaveTrack),
|
|
AudioIONotBusyFlag, wxT("Ctrl+Shift+N") );
|
|
c->AddItem( wxT("NewStereoTrack"), XXO("&Stereo Track"),
|
|
FN(OnNewStereoTrack), AudioIONotBusyFlag );
|
|
c->AddItem( wxT("NewLabelTrack"), XXO("&Label Track"),
|
|
FN(OnNewLabelTrack), AudioIONotBusyFlag );
|
|
c->AddItem( wxT("NewTimeTrack"), XXO("&Time Track"),
|
|
FN(OnNewTimeTrack), AudioIONotBusyFlag );
|
|
|
|
c->EndSubMenu();
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
c->AddSeparator();
|
|
|
|
c->BeginSubMenu(_("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.
|
|
const PluginID ID = EffectManager::Get().GetEffectByIdentifier(wxT("StereoToMono"));
|
|
const PluginDescriptor *plug = PluginManager::Get().GetPlugin(ID);
|
|
if (plug && plug->IsEnabled())
|
|
c->AddItem( wxT("Stereo to Mono"), XXO("Mix Stereo Down to &Mono"), FN(OnStereoToMono),
|
|
AudioIONotBusyFlag | StereoRequiredFlag | WaveTracksSelectedFlag );
|
|
}
|
|
c->AddItem( wxT("MixAndRender"), XXO("Mi&x and Render"), FN(OnMixAndRender),
|
|
AudioIONotBusyFlag | WaveTracksSelectedFlag );
|
|
c->AddItem( wxT("MixAndRenderToNewTrack"), XXO("Mix and Render to Ne&w Track"), FN(OnMixAndRenderToNewTrack),
|
|
AudioIONotBusyFlag | WaveTracksSelectedFlag, wxT("Ctrl+Shift+M") );
|
|
c->EndSubMenu();
|
|
|
|
c->AddItem( wxT("Resample"), XXO("&Resample..."), FN(OnResample),
|
|
AudioIONotBusyFlag | WaveTracksSelectedFlag );
|
|
|
|
c->AddSeparator();
|
|
|
|
c->AddItem( wxT("RemoveTracks"), XXO("Remo&ve Tracks"), FN(OnRemoveTracks),
|
|
AudioIONotBusyFlag | TracksSelectedFlag );
|
|
|
|
c->AddSeparator();
|
|
|
|
c->BeginSubMenu(_("M&ute/Unmute"));
|
|
c->AddItem( wxT("MuteAllTracks"), XXO("&Mute All Tracks"),
|
|
FN(OnMuteAllTracks), AudioIONotBusyFlag, wxT("Ctrl+U") );
|
|
c->AddItem( wxT("UnmuteAllTracks"), XXO("&Unmute All Tracks"),
|
|
FN(OnUnmuteAllTracks), AudioIONotBusyFlag, wxT("Ctrl+Shift+U") );
|
|
c->EndSubMenu();
|
|
|
|
c->BeginSubMenu(_("&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 selcted.
|
|
c->AddItem( wxT("PanLeft"), XXO("&Left"), FN(OnPanLeft),
|
|
TracksSelectedFlag,
|
|
Options{}.LongName( _("Pan Left") ) );
|
|
c->AddItem( wxT("PanRight"), XXO("&Right"), FN(OnPanRight),
|
|
TracksSelectedFlag,
|
|
Options{}.LongName( _("Pan Right") ) );
|
|
c->AddItem( wxT("PanCenter"), XXO("&Center"), FN(OnPanCenter),
|
|
TracksSelectedFlag,
|
|
Options{}.LongName( _("Pan Center") ) );
|
|
c->EndSubMenu();
|
|
|
|
|
|
c->AddSeparator();
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
const TranslatedInternalString alignLabelsNoSync[] = {
|
|
{ wxT("EndToEnd"), _("&Align End to End") },
|
|
{ wxT("Together"), _("Align &Together") },
|
|
};
|
|
|
|
const TranslatedInternalString alignLabels[] = {
|
|
{ wxT("StartToZero"), _("Start to &Zero") },
|
|
{ wxT("StartToSelStart"), _("Start to &Cursor/Selection Start") },
|
|
{ wxT("StartToSelEnd"), _("Start to Selection &End") },
|
|
{ wxT("EndToSelStart"), _("End to Cu&rsor/Selection Start") },
|
|
{ wxT("EndToSelEnd"), _("End to Selection En&d") },
|
|
};
|
|
mAlignLabelsCount = sizeof(alignLabels) / sizeof(alignLabels[0]);
|
|
|
|
c->BeginSubMenu(_("&Align Tracks"));
|
|
|
|
//c->BeginSubMenu(_("Just Move Tracks"));
|
|
c->AddItemList(wxT("Align"), alignLabelsNoSync, 2u, FN(OnAlignNoSync),
|
|
AudioIONotBusyFlag | TracksSelectedFlag);
|
|
c->AddSeparator();
|
|
c->AddItemList(wxT("Align"), alignLabels, mAlignLabelsCount, FN(OnAlign),
|
|
AudioIONotBusyFlag | TracksSelectedFlag);
|
|
c->AddSeparator();
|
|
c->AddItem( wxT("MoveSelectionWithTracks"), XXO("&Move Selection with Tracks (on/off)"),
|
|
FN(OnMoveSelectionWithTracks),
|
|
AlwaysEnabledFlag,
|
|
Options{}.CheckState( gPrefs->Read(wxT("/GUI/MoveSelectionWithTracks"), 0L ) ) );
|
|
c->EndSubMenu();
|
|
|
|
#if 0
|
|
// TODO: Can these labels be made clearer? Do we need this sub-menu at all?
|
|
c->BeginSubMenu(_("Move Sele&ction and Tracks"));
|
|
|
|
c->AddItemList(wxT("AlignMove"), alignLabels, mAlignLabelsCount, FN(OnAlignMoveSel));
|
|
c->SetCommandFlags(wxT("AlignMove"),
|
|
AudioIONotBusyFlag | TracksSelectedFlag,
|
|
AudioIONotBusyFlag | TracksSelectedFlag);
|
|
|
|
c->EndSubMenu();
|
|
#endif
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
#ifdef EXPERIMENTAL_SCOREALIGN
|
|
c->AddItem( wxT("ScoreAlign"), XXO("Synchronize MIDI with Audio"), FN(OnScoreAlign),
|
|
AudioIONotBusyFlag | NoteTracksSelectedFlag | WaveTracksSelectedFlag );
|
|
#endif // EXPERIMENTAL_SCOREALIGN
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
c->BeginSubMenu(_("S&ort Tracks"));
|
|
|
|
c->AddItem( wxT("SortByTime"), XXO("By &Start Time"), FN(OnSortTime),
|
|
TracksExistFlag,
|
|
Options{}.LongName( _("Sort by Time") ) );
|
|
c->AddItem( wxT("SortByName"), XXO("By &Name"), FN(OnSortName),
|
|
TracksExistFlag,
|
|
Options{}.LongName( _("Sort by Name") ) );
|
|
|
|
c->EndSubMenu();
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
#ifdef EXPERIMENTAL_SYNC_LOCK
|
|
c->AddSeparator();
|
|
c->AddItem( wxT("SyncLock"), XXO("Sync-&Lock Tracks (on/off)"), FN(OnSyncLock),
|
|
AlwaysEnabledFlag,
|
|
Options{}.CheckState( gPrefs->Read(wxT("/GUI/SyncLockTracks"), 0L) ) );
|
|
|
|
#endif
|
|
|
|
c->EndMenu();
|
|
|
|
// All of this is a bit hacky until we can get more things connected into
|
|
// the plugin manager...sorry! :-(
|
|
|
|
wxArrayString defaults;
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Generate Menu
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
c->BeginMenu(_("&Generate"));
|
|
|
|
#ifdef EXPERIMENTAL_EFFECT_MANAGEMENT
|
|
c->AddItem( wxT("ManageGenerators"), XXO("Add / Remove Plug-ins..."),
|
|
FN(OnManageGenerators), AudioIONotBusyFlag );
|
|
c->AddSeparator();
|
|
#endif
|
|
|
|
|
|
PopulateEffectsMenu(c,
|
|
EffectTypeGenerate,
|
|
AudioIONotBusyFlag,
|
|
AudioIONotBusyFlag);
|
|
|
|
c->EndMenu();
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Effect Menu
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
c->BeginMenu(_("Effe&ct"));
|
|
|
|
wxString buildMenuLabel;
|
|
if (!mLastEffect.IsEmpty()) {
|
|
buildMenuLabel.Printf(_("Repeat %s"),
|
|
EffectManager::Get().GetCommandName(mLastEffect));
|
|
}
|
|
else
|
|
buildMenuLabel = _("Repeat Last Effect");
|
|
|
|
#ifdef EXPERIMENTAL_EFFECT_MANAGEMENT
|
|
c->AddItem( wxT("ManageEffects"), XXO("Add / Remove Plug-ins..."),
|
|
FN(OnManageEffects), AudioIONotBusyFlag );
|
|
c->AddSeparator();
|
|
#endif
|
|
|
|
c->AddItem( wxT("RepeatLastEffect"), buildMenuLabel, false, FN(OnRepeatLastEffect),
|
|
AudioIONotBusyFlag | TimeSelectedFlag | WaveTracksSelectedFlag | HasLastEffectFlag, wxT("Ctrl+R") );
|
|
|
|
c->AddSeparator();
|
|
|
|
PopulateEffectsMenu(c,
|
|
EffectTypeProcess,
|
|
AudioIONotBusyFlag | TimeSelectedFlag | WaveTracksSelectedFlag,
|
|
IsRealtimeNotActiveFlag);
|
|
|
|
c->EndMenu();
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Analyze Menu
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
c->BeginMenu(_("&Analyze"));
|
|
|
|
#ifdef EXPERIMENTAL_EFFECT_MANAGEMENT
|
|
c->AddItem( wxT("ManageAnalyzers"), XXO("Add / Remove Plug-ins..."),
|
|
FN(OnManageAnalyzers), AudioIONotBusyFlag );
|
|
c->AddSeparator();
|
|
#endif
|
|
|
|
|
|
c->AddItem( wxT("ContrastAnalyser"), XXO("Contrast..."), FN(OnContrast),
|
|
AudioIONotBusyFlag | WaveTracksSelectedFlag | TimeSelectedFlag, wxT("Ctrl+Shift+T") );
|
|
c->AddItem( wxT("PlotSpectrum"), XXO("Plot Spectrum..."), FN(OnPlotSpectrum),
|
|
AudioIONotBusyFlag | WaveTracksSelectedFlag | TimeSelectedFlag );
|
|
|
|
PopulateEffectsMenu(c,
|
|
EffectTypeAnalyze,
|
|
AudioIONotBusyFlag | TimeSelectedFlag | WaveTracksSelectedFlag,
|
|
IsRealtimeNotActiveFlag);
|
|
|
|
c->EndMenu();
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Tools Menu
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
c->BeginMenu(_("T&ools"));
|
|
|
|
#ifdef EXPERIMENTAL_EFFECT_MANAGEMENT
|
|
c->AddItem( wxT("ManageTools"), XXO("Add / Remove Plug-ins..."),
|
|
FN(OnManageTools), AudioIONotBusyFlag );
|
|
//c->AddSeparator();
|
|
#endif
|
|
|
|
c->AddItem( wxT("ManageMacros"), XXO("&Macros..."),
|
|
FN(OnManageMacros), AudioIONotBusyFlag );
|
|
|
|
c->BeginSubMenu(_("&Apply Macro"));
|
|
c->AddItem( wxT("ApplyMacrosPalette"), XXO("&Palette..."),
|
|
FN(OnApplyMacrosPalette), AudioIONotBusyFlag );
|
|
c->AddSeparator();
|
|
PopulateMacrosMenu( c, AudioIONotBusyFlag );
|
|
c->EndSubMenu();
|
|
c->AddSeparator();
|
|
|
|
c->AddItem( 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.
|
|
c->AddItem( wxT("Benchmark"), XXO("&Run Benchmark..."),
|
|
FN(OnBenchmark), AudioIONotBusyFlag );
|
|
//#endif
|
|
|
|
c->AddSeparator();
|
|
|
|
PopulateEffectsMenu(c,
|
|
EffectTypeTool,
|
|
AudioIONotBusyFlag,
|
|
AudioIONotBusyFlag);
|
|
|
|
#ifdef IS_ALPHA
|
|
c->AddSeparator();
|
|
c->AddItem( wxT("SimulateRecordingErrors"),
|
|
XXO("Simulate Recording Errors"),
|
|
FN(OnSimulateRecordingErrors),
|
|
AudioIONotBusyFlag,
|
|
Options{}.CheckState( gAudioIO->mSimulateRecordingErrors ) );
|
|
c->AddItem( wxT("DetectUpstreamDropouts"),
|
|
XXO("Detect Upstream Dropouts"),
|
|
FN(OnDetectUpstreamDropouts),
|
|
AudioIONotBusyFlag,
|
|
Options{}.CheckState( gAudioIO->mDetectUpstreamDropouts ) );
|
|
#endif
|
|
|
|
c->EndMenu();
|
|
|
|
|
|
#ifdef __WXMAC__
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// poor imitation of the Mac Windows Menu
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
{
|
|
c->BeginMenu(_("&Window"));
|
|
/* i18n-hint: Standard Macintosh Window menu item: Make (the current
|
|
* window) shrink to an icon on the dock */
|
|
c->AddItem( wxT("MacMinimize"), XXO("&Minimize"), FN(OnMacMinimize), NotMinimizedFlag,
|
|
wxT("Ctrl+M") );
|
|
/* i18n-hint: Standard Macintosh Window menu item: Make (the current
|
|
* window) full sized */
|
|
c->AddItem( wxT("MacZoom"), XXO("&Zoom"), FN(OnMacZoom),
|
|
NotMinimizedFlag );
|
|
c->AddSeparator();
|
|
/* i18n-hint: Standard Macintosh Window menu item: Make all project
|
|
* windows un-hidden */
|
|
c->AddItem( wxT("MacBringAllToFront"),
|
|
XXO("&Bring All to Front"), FN(OnMacBringAllToFront),
|
|
AlwaysEnabledFlag );
|
|
c->EndMenu();
|
|
}
|
|
#endif
|
|
|
|
|
|
bool bShowExtraMenus;
|
|
gPrefs->Read(wxT("/GUI/ShowExtraMenus"), &bShowExtraMenus, false);
|
|
std::unique_ptr<wxMenuBar> menubar2;
|
|
if( !bShowExtraMenus )
|
|
{
|
|
menubar2 = c->AddMenuBar(wxT("ext-menu"));
|
|
c->SetOccultCommands(true);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Ext-Menu
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
// i18n-hint: Extra is a menu with extra commands
|
|
c->BeginMenu(_("Ext&ra"));
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
c->BeginSubMenu(_("T&ransport"));
|
|
|
|
// PlayStop is already in the menus.
|
|
/* i18n-hint: (verb) Start playing audio*/
|
|
c->AddItem( wxT("Play"), XXO("Pl&ay"), FN(OnPlayStop),
|
|
WaveTracksExistFlag | AudioIONotBusyFlag );
|
|
/* i18n-hint: (verb) Stop playing audio*/
|
|
c->AddItem( wxT("Stop"), XXO("Sto&p"), FN(OnStop),
|
|
AudioIOBusyFlag | CanStopAudioStreamFlag );
|
|
|
|
c->AddItem( wxT("PlayOneSec"), XXO("Play &One Second"), FN(OnPlayOneSecond),
|
|
CaptureNotBusyFlag, wxT("1") );
|
|
c->AddItem( wxT("PlayToSelection"), XXO("Play to &Selection"), FN(OnPlayToSelection),
|
|
CaptureNotBusyFlag, wxT("B") );
|
|
c->AddItem( wxT("PlayBeforeSelectionStart"),
|
|
XXO("Play &Before Selection Start"), FN(OnPlayBeforeSelectionStart),
|
|
CaptureNotBusyFlag, wxT("Shift+F5") );
|
|
c->AddItem( wxT("PlayAfterSelectionStart"),
|
|
XXO("Play Af&ter Selection Start"), FN(OnPlayAfterSelectionStart),
|
|
CaptureNotBusyFlag, wxT("Shift+F6") );
|
|
c->AddItem( wxT("PlayBeforeSelectionEnd"),
|
|
XXO("Play Be&fore Selection End"), FN(OnPlayBeforeSelectionEnd),
|
|
CaptureNotBusyFlag, wxT("Shift+F7") );
|
|
c->AddItem( wxT("PlayAfterSelectionEnd"),
|
|
XXO("Play Aft&er Selection End"), FN(OnPlayAfterSelectionEnd),
|
|
CaptureNotBusyFlag, wxT("Shift+F8") );
|
|
c->AddItem( wxT("PlayBeforeAndAfterSelectionStart"),
|
|
XXO("Play Before a&nd After Selection Start"),
|
|
FN(OnPlayBeforeAndAfterSelectionStart), CaptureNotBusyFlag,
|
|
wxT("Ctrl+Shift+F5") );
|
|
c->AddItem( wxT("PlayBeforeAndAfterSelectionEnd"),
|
|
XXO("Play Before an&d After Selection End"),
|
|
FN(OnPlayBeforeAndAfterSelectionEnd), CaptureNotBusyFlag,
|
|
wxT("Ctrl+Shift+F7") );
|
|
c->AddItem( wxT("PlayCutPreview"), XXO("Play C&ut Preview"), FN(OnPlayCutPreview),
|
|
CaptureNotBusyFlag, wxT("C") );
|
|
c->EndSubMenu();
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
c->BeginSubMenu(_("T&ools"));
|
|
|
|
c->AddItem( wxT("SelectTool"), XXO("&Selection Tool"), FN(OnSelectTool),
|
|
AlwaysEnabledFlag, wxT("F1") );
|
|
c->AddItem( wxT("EnvelopeTool"), XXO("&Envelope Tool"),
|
|
FN(OnEnvelopeTool), AlwaysEnabledFlag, wxT("F2") );
|
|
c->AddItem( wxT("DrawTool"), XXO("&Draw Tool"), FN(OnDrawTool),
|
|
AlwaysEnabledFlag, wxT("F3") );
|
|
c->AddItem( wxT("ZoomTool"), XXO("&Zoom Tool"), FN(OnZoomTool),
|
|
AlwaysEnabledFlag, wxT("F4") );
|
|
c->AddItem( wxT("TimeShiftTool"), XXO("&Time Shift Tool"),
|
|
FN(OnTimeShiftTool), AlwaysEnabledFlag, wxT("F5") );
|
|
c->AddItem( wxT("MultiTool"), XXO("&Multi Tool"), FN(OnMultiTool),
|
|
AlwaysEnabledFlag, wxT("F6") );
|
|
|
|
c->AddItem( wxT("PrevTool"), XXO("&Previous Tool"), FN(OnPrevTool),
|
|
AlwaysEnabledFlag, wxT("A") );
|
|
c->AddItem( wxT("NextTool"), XXO("&Next Tool"), FN(OnNextTool),
|
|
AlwaysEnabledFlag, wxT("D") );
|
|
c->EndSubMenu();
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
c->BeginSubMenu(_("Mi&xer"));
|
|
|
|
c->AddItem( wxT("OutputGain"), XXO("Ad&just Playback Volume..."),
|
|
FN(OnOutputGain), AlwaysEnabledFlag );
|
|
c->AddItem( wxT("OutputGainInc"), XXO("&Increase Playback Volume"),
|
|
FN(OnOutputGainInc), AlwaysEnabledFlag );
|
|
c->AddItem( wxT("OutputGainDec"), XXO("&Decrease Playback Volume"),
|
|
FN(OnOutputGainDec), AlwaysEnabledFlag );
|
|
c->AddItem( wxT("InputGain"), XXO("Adj&ust Recording Volume..."),
|
|
FN(OnInputGain), AlwaysEnabledFlag );
|
|
c->AddItem( wxT("InputGainInc"), XXO("I&ncrease Recording Volume"),
|
|
FN(OnInputGainInc), AlwaysEnabledFlag );
|
|
c->AddItem( wxT("InputGainDec"), XXO("D&ecrease Recording Volume"),
|
|
FN(OnInputGainDec), AlwaysEnabledFlag );
|
|
c->EndSubMenu();
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
c->BeginSubMenu(_("&Edit"));
|
|
|
|
c->AddItem( wxT("DeleteKey"), XXO("&Delete Key"), FN(OnDelete),
|
|
AudioIONotBusyFlag | TracksSelectedFlag | TimeSelectedFlag | NoAutoSelect,
|
|
Options{ wxT("Backspace") }
|
|
.Mask( AudioIONotBusyFlag | TracksSelectedFlag | TimeSelectedFlag ) );
|
|
|
|
c->AddItem( wxT("DeleteKey2"), XXO("Delete Key&2"), FN(OnDelete),
|
|
AudioIONotBusyFlag | TracksSelectedFlag | TimeSelectedFlag | NoAutoSelect,
|
|
Options{ wxT("Delete") }
|
|
.Mask( AudioIONotBusyFlag | TracksSelectedFlag | TimeSelectedFlag ) );
|
|
c->EndSubMenu();
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
c->BeginSubMenu(_("&Play-at-Speed"));
|
|
|
|
/* i18n-hint: 'Normal Play-at-Speed' doesn't loop or cut preview. */
|
|
c->AddItem( wxT("PlayAtSpeed"), XXO("Normal Pl&ay-at-Speed"),
|
|
FN(OnPlayAtSpeed), CaptureNotBusyFlag );
|
|
c->AddItem( wxT("PlayAtSpeedLooped"), XXO("&Loop Play-at-Speed"),
|
|
FN(OnPlayAtSpeedLooped), CaptureNotBusyFlag );
|
|
c->AddItem( wxT("PlayAtSpeedCutPreview"), XXO("Play C&ut Preview-at-Speed"),
|
|
FN(OnPlayAtSpeedCutPreview), CaptureNotBusyFlag );
|
|
c->AddItem( wxT("SetPlaySpeed"), XXO("Ad&just Playback Speed..."),
|
|
FN(OnSetPlaySpeed), CaptureNotBusyFlag );
|
|
c->AddItem( wxT("PlaySpeedInc"), XXO("&Increase Playback Speed"),
|
|
FN(OnPlaySpeedInc), CaptureNotBusyFlag );
|
|
c->AddItem( wxT("PlaySpeedDec"), XXO("&Decrease Playback Speed"),
|
|
FN(OnPlaySpeedDec), CaptureNotBusyFlag );
|
|
|
|
// These were on the original transcription toolbar. But they are not on the
|
|
// shortened one.
|
|
c->AddItem( wxT("MoveToPrevLabel"), XXO("Move to &Previous Label"), FN(OnMoveToPrevLabel),
|
|
CaptureNotBusyFlag | TrackPanelHasFocus, wxT("Alt+Left") );
|
|
c->AddItem( wxT("MoveToNextLabel"), XXO("Move to &Next Label"), FN(OnMoveToNextLabel),
|
|
CaptureNotBusyFlag | TrackPanelHasFocus, wxT("Alt+Right") );
|
|
c->EndSubMenu();
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
c->BeginSubMenu(_("See&k"));
|
|
|
|
c->AddItem( wxT("SeekLeftShort"), XXO("Short Seek &Left During Playback"),
|
|
FN(OnSeekLeftShort), AudioIOBusyFlag, wxT("Left\tallowDup") );
|
|
c->AddItem( wxT("SeekRightShort"),
|
|
XXO("Short Seek &Right During Playback"), FN(OnSeekRightShort),
|
|
AudioIOBusyFlag, wxT("Right\tallowDup") );
|
|
c->AddItem( wxT("SeekLeftLong"), XXO("Long Seek Le&ft During Playback"),
|
|
FN(OnSeekLeftLong), AudioIOBusyFlag, wxT("Shift+Left\tallowDup") );
|
|
c->AddItem( wxT("SeekRightLong"), XXO("Long Seek Rig&ht During Playback"),
|
|
FN(OnSeekRightLong), AudioIOBusyFlag, wxT("Shift+Right\tallowDup") );
|
|
c->EndSubMenu();
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
c->BeginSubMenu(_("De&vice"));
|
|
|
|
c->AddItem( wxT("InputDevice"), XXO("Change &Recording Device..."), FN(OnInputDevice),
|
|
AudioIONotBusyFlag, wxT("Shift+I") );
|
|
c->AddItem( wxT("OutputDevice"), XXO("Change &Playback Device..."), FN(OnOutputDevice),
|
|
AudioIONotBusyFlag, wxT("Shift+O") );
|
|
c->AddItem( wxT("AudioHost"), XXO("Change Audio &Host..."), FN(OnAudioHost),
|
|
AudioIONotBusyFlag, wxT("Shift+H") );
|
|
c->AddItem( wxT("InputChannels"), XXO("Change Recording Cha&nnels..."), FN(OnInputChannels),
|
|
AudioIONotBusyFlag, wxT("Shift+N") );
|
|
c->EndSubMenu();
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
c->BeginSubMenu(_("&Selection"));
|
|
|
|
c->AddItem( wxT("SnapToOff"), XXO("Snap-To &Off"), FN(OnSnapToOff),
|
|
AlwaysEnabledFlag );
|
|
c->AddItem( wxT("SnapToNearest"), XXO("Snap-To &Nearest"),
|
|
FN(OnSnapToNearest), AlwaysEnabledFlag );
|
|
c->AddItem( wxT("SnapToPrior"), XXO("Snap-To &Prior"), FN(OnSnapToPrior),
|
|
AlwaysEnabledFlag );
|
|
|
|
c->AddItem( wxT("SelStart"), XXO("Selection to &Start"), FN(OnSelToStart),
|
|
AlwaysEnabledFlag, wxT("Shift+Home") );
|
|
c->AddItem( wxT("SelEnd"), XXO("Selection to En&d"), FN(OnSelToEnd),
|
|
AlwaysEnabledFlag, wxT("Shift+End") );
|
|
c->AddItem( wxT("SelExtLeft"), XXO("Selection Extend &Left"), FN(OnSelExtendLeft),
|
|
TracksExistFlag | TrackPanelHasFocus, wxT("Shift+Left\twantKeyup\tallowDup") );
|
|
c->AddItem( wxT("SelExtRight"), XXO("Selection Extend &Right"), FN(OnSelExtendRight),
|
|
TracksExistFlag | TrackPanelHasFocus, wxT("Shift+Right\twantKeyup\tallowDup") );
|
|
|
|
c->AddItem( wxT("SelSetExtLeft"), XXO("Set (or Extend) Le&ft Selection"), FN(OnSelSetExtendLeft),
|
|
TracksExistFlag | TrackPanelHasFocus );
|
|
c->AddItem( wxT("SelSetExtRight"), XXO("Set (or Extend) Rig&ht Selection"), FN(OnSelSetExtendRight),
|
|
TracksExistFlag | TrackPanelHasFocus );
|
|
|
|
c->AddItem( wxT("SelCntrLeft"), XXO("Selection Contract L&eft"), FN(OnSelContractLeft),
|
|
TracksExistFlag | TrackPanelHasFocus, wxT("Ctrl+Shift+Right\twantKeyup") );
|
|
c->AddItem( wxT("SelCntrRight"), XXO("Selection Contract R&ight"), FN(OnSelContractRight),
|
|
TracksExistFlag | TrackPanelHasFocus, wxT("Ctrl+Shift+Left\twantKeyup") );
|
|
|
|
c->EndSubMenu();
|
|
|
|
|
|
c->AddSeparator();
|
|
|
|
c->AddGlobalCommand(wxT("PrevWindow"), XXO("Move Backward Through Active Windows"), FN(OnPrevWindow), wxT("Alt+Shift+F6"));
|
|
c->AddGlobalCommand(wxT("NextWindow"), XXO("Move Forward Through Active Windows"), FN(OnNextWindow), wxT("Alt+F6"));
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
c->BeginSubMenu(_("F&ocus"));
|
|
|
|
c->AddItem( wxT("PrevFrame"),
|
|
XXO("Move &Backward from Toolbars to Tracks"), FN(OnPrevFrame),
|
|
AlwaysEnabledFlag, wxT("Ctrl+Shift+F6") );
|
|
c->AddItem( wxT("NextFrame"),
|
|
XXO("Move F&orward from Toolbars to Tracks"), FN(OnNextFrame),
|
|
AlwaysEnabledFlag, wxT("Ctrl+F6") );
|
|
|
|
constexpr auto FocusedTracksFlags = TracksExistFlag | TrackPanelHasFocus;
|
|
c->AddItem( wxT("PrevTrack"), XXO("Move Focus to &Previous Track"),
|
|
FN(OnCursorUp), FocusedTracksFlags, wxT("Up") );
|
|
c->AddItem( wxT("NextTrack"), XXO("Move Focus to &Next Track"),
|
|
FN(OnCursorDown), FocusedTracksFlags, wxT("Down") );
|
|
c->AddItem( wxT("FirstTrack"), XXO("Move Focus to &First Track"),
|
|
FN(OnFirstTrack), FocusedTracksFlags, wxT("Ctrl+Home") );
|
|
c->AddItem( wxT("LastTrack"), XXO("Move Focus to &Last Track"),
|
|
FN(OnLastTrack), FocusedTracksFlags, wxT("Ctrl+End") );
|
|
|
|
c->AddItem( wxT("ShiftUp"), XXO("Move Focus to P&revious and Select"),
|
|
FN(OnShiftUp), FocusedTracksFlags, wxT("Shift+Up") );
|
|
c->AddItem( wxT("ShiftDown"), XXO("Move Focus to N&ext and Select"),
|
|
FN(OnShiftDown), FocusedTracksFlags, wxT("Shift+Down") );
|
|
|
|
c->AddItem( wxT("Toggle"), XXO("&Toggle Focused Track"), FN(OnToggle),
|
|
FocusedTracksFlags, wxT("Return") );
|
|
c->AddItem( wxT("ToggleAlt"), XXO("Toggle Focuse&d Track"), FN(OnToggle),
|
|
FocusedTracksFlags, wxT("NUMPAD_ENTER") );
|
|
c->EndSubMenu();
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
c->BeginSubMenu(_("&Cursor"));
|
|
|
|
c->AddItem( wxT("CursorLeft"), XXO("Cursor &Left"), FN(OnCursorLeft),
|
|
TracksExistFlag | TrackPanelHasFocus, wxT("Left\twantKeyup\tallowDup") );
|
|
c->AddItem( wxT("CursorRight"), XXO("Cursor &Right"), FN(OnCursorRight),
|
|
TracksExistFlag | TrackPanelHasFocus, wxT("Right\twantKeyup\tallowDup") );
|
|
c->AddItem( wxT("CursorShortJumpLeft"), XXO("Cursor Sh&ort Jump Left"), FN(OnCursorShortJumpLeft),
|
|
TracksExistFlag | TrackPanelHasFocus, wxT(",") );
|
|
c->AddItem( wxT("CursorShortJumpRight"), XXO("Cursor Shor&t Jump Right"), FN(OnCursorShortJumpRight),
|
|
TracksExistFlag | TrackPanelHasFocus, wxT(".") );
|
|
c->AddItem( wxT("CursorLongJumpLeft"), XXO("Cursor Long J&ump Left"), FN(OnCursorLongJumpLeft),
|
|
TracksExistFlag | TrackPanelHasFocus, wxT("Shift+,") );
|
|
c->AddItem( wxT("CursorLongJumpRight"), XXO("Cursor Long Ju&mp Right"), FN(OnCursorLongJumpRight),
|
|
TracksExistFlag | TrackPanelHasFocus, wxT("Shift+.") );
|
|
|
|
c->AddItem( wxT("ClipLeft"), XXO("Clip L&eft"), FN(OnClipLeft),
|
|
TracksExistFlag | TrackPanelHasFocus, wxT("\twantKeyup") );
|
|
c->AddItem( wxT("ClipRight"), XXO("Clip Rig&ht"), FN(OnClipRight),
|
|
TracksExistFlag | TrackPanelHasFocus, wxT("\twantKeyup") );
|
|
c->EndSubMenu();
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
c->BeginSubMenu(_("&Track"));
|
|
|
|
c->AddItem( wxT("TrackPan"), XXO("Change P&an on Focused Track..."), FN(OnTrackPan),
|
|
TrackPanelHasFocus | TracksExistFlag, wxT("Shift+P") );
|
|
c->AddItem( wxT("TrackPanLeft"), XXO("Pan &Left on Focused Track"), FN(OnTrackPanLeft),
|
|
TrackPanelHasFocus | TracksExistFlag, wxT("Alt+Shift+Left") );
|
|
c->AddItem( wxT("TrackPanRight"), XXO("Pan &Right on Focused Track"), FN(OnTrackPanRight),
|
|
TrackPanelHasFocus | TracksExistFlag, wxT("Alt+Shift+Right") );
|
|
c->AddItem( wxT("TrackGain"), XXO("Change Gai&n on Focused Track..."), FN(OnTrackGain),
|
|
TrackPanelHasFocus | TracksExistFlag, wxT("Shift+G") );
|
|
c->AddItem( wxT("TrackGainInc"), XXO("&Increase Gain on Focused Track"), FN(OnTrackGainInc),
|
|
TrackPanelHasFocus | TracksExistFlag, wxT("Alt+Shift+Up") );
|
|
c->AddItem( wxT("TrackGainDec"), XXO("&Decrease Gain on Focused Track"), FN(OnTrackGainDec),
|
|
TrackPanelHasFocus | TracksExistFlag, wxT("Alt+Shift+Down") );
|
|
c->AddItem( wxT("TrackMenu"), XXO("Op&en Menu on Focused Track..."), FN(OnTrackMenu),
|
|
TracksExistFlag | TrackPanelHasFocus, wxT("Shift+M\tskipKeydown") );
|
|
c->AddItem( wxT("TrackMute"), XXO("M&ute/Unmute Focused Track"), FN(OnTrackMute),
|
|
TracksExistFlag | TrackPanelHasFocus, wxT("Shift+U") );
|
|
c->AddItem( wxT("TrackSolo"), XXO("&Solo/Unsolo Focused Track"), FN(OnTrackSolo),
|
|
TracksExistFlag | TrackPanelHasFocus, wxT("Shift+S") );
|
|
c->AddItem( wxT("TrackClose"), XXO("&Close Focused Track"), FN(OnTrackClose),
|
|
AudioIONotBusyFlag | TrackPanelHasFocus | TracksExistFlag, wxT("Shift+C") );
|
|
c->AddItem( wxT("TrackMoveUp"), XXO("Move Focused Track U&p"), FN(OnTrackMoveUp),
|
|
AudioIONotBusyFlag | TrackPanelHasFocus | TracksExistFlag );
|
|
c->AddItem( wxT("TrackMoveDown"), XXO("Move Focused Track Do&wn"), FN(OnTrackMoveDown),
|
|
AudioIONotBusyFlag | TrackPanelHasFocus | TracksExistFlag );
|
|
c->AddItem( wxT("TrackMoveTop"), XXO("Move Focused Track to T&op"), FN(OnTrackMoveTop),
|
|
AudioIONotBusyFlag | TrackPanelHasFocus | TracksExistFlag );
|
|
c->AddItem( wxT("TrackMoveBottom"), XXO("Move Focused Track to &Bottom"), FN(OnTrackMoveBottom),
|
|
AudioIONotBusyFlag | TrackPanelHasFocus | TracksExistFlag );
|
|
c->EndSubMenu();
|
|
|
|
// These are the more useful to VI user Scriptables.
|
|
// i18n-hint: Scriptables are commands normally used from Python, Perl etc.
|
|
c->BeginSubMenu(_("&Scriptables 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.)
|
|
|
|
c->AddItem( wxT("SelectTime"), XXO("Select Time..."), FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag );
|
|
c->AddItem( wxT("SelectFrequencies"), XXO("Select Frequencies..."), FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag );
|
|
c->AddItem( wxT("SelectTracks"), XXO("Select Tracks..."), FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag );
|
|
|
|
c->AddItem( wxT("SetTrackStatus"), XXO("Set Track Status..."), FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag );
|
|
c->AddItem( wxT("SetTrackAudio"), XXO("Set Track Audio..."), FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag );
|
|
c->AddItem( wxT("SetTrackVisuals"), XXO("Set Track Visuals..."), FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag );
|
|
|
|
|
|
c->AddItem( wxT("GetPreference"), XXO("Get Preference..."), FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag );
|
|
c->AddItem( wxT("SetPreference"), XXO("Set Preference..."), FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag );
|
|
c->AddItem( wxT("SetClip"), XXO("Set Clip..."), FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag );
|
|
c->AddItem( wxT("SetEnvelope"), XXO("Set Envelope..."), FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag );
|
|
c->AddItem( wxT("SetLabel"), XXO("Set Label..."), FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag );
|
|
c->AddItem( wxT("SetProject"), XXO("Set Project..."), FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag );
|
|
|
|
c->EndSubMenu();
|
|
// Less useful to VI users.
|
|
c->BeginSubMenu(_("Scripta&bles II"));
|
|
|
|
c->AddItem( wxT("Select"), XXO("Select..."), FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag );
|
|
c->AddItem( wxT("SetTrack"), XXO("Set Track..."), FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag );
|
|
c->AddItem( wxT("GetInfo"), XXO("Get Info..."), FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag );
|
|
c->AddItem( wxT("Message"), XXO("Message..."), FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag );
|
|
c->AddItem( wxT("Help"), XXO("Help..."), FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag );
|
|
|
|
c->AddItem( wxT("Import2"), XXO("Import..."), FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag );
|
|
c->AddItem( wxT("Export2"), XXO("Export..."), FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag );
|
|
c->AddItem( wxT("OpenProject2"), XXO("Open Project..."), FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag );
|
|
c->AddItem( wxT("SaveProject2"), XXO("Save Project..."), FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag );
|
|
|
|
c->AddItem( wxT("Drag"), XXO("Move Mouse..."), FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag );
|
|
c->AddItem( wxT("CompareAudio"), XXO("Compare Audio..."), FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag );
|
|
// i18n-hint: Screenshot in the help menu has a much bigger dialog.
|
|
c->AddItem( wxT("Screenshot"), XXO("Screenshot (short format)..."), FN(OnAudacityCommand),
|
|
AudioIONotBusyFlag );
|
|
|
|
|
|
c->EndSubMenu();
|
|
|
|
|
|
// Accel key is not bindable.
|
|
c->AddItem( wxT("FullScreenOnOff"), XXO("&Full Screen (on/off)"), FN(OnFullScreen),
|
|
AlwaysEnabledFlag,
|
|
Options{
|
|
#ifdef __WXMAC__
|
|
wxT("Ctrl+/"),
|
|
#else
|
|
wxT("F11"),
|
|
#endif
|
|
}
|
|
.CheckState( project.wxTopLevelWindow::IsFullScreen() ) );
|
|
|
|
#ifdef __WXMAC__
|
|
/* i18n-hint: Shrink all project windows to icons on the Macintosh tooldock */
|
|
c->AddItem( wxT("MacMinimizeAll"), XXO("Minimize All Projects"),
|
|
FN(OnMacMinimizeAll),
|
|
AlwaysEnabledFlag, wxT("Ctrl+Alt+M") );
|
|
#endif
|
|
|
|
|
|
|
|
c->EndMenu();
|
|
|
|
|
|
|
|
if (!bShowExtraMenus)
|
|
{
|
|
c->SwapMenuBars();
|
|
c->SetOccultCommands(false);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Help Menu
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
#ifdef __WXMAC__
|
|
wxGetApp().s_macHelpMenuTitleName = _("&Help");
|
|
#endif
|
|
|
|
c->BeginMenu(_("&Help"));
|
|
|
|
// 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.
|
|
c->AddItem( wxT("QuickHelp"), XXO("&Getting Started"), FN(OnQuickHelp) );
|
|
c->AddItem( wxT("Manual"), XXO("Audacity &Manual"), FN(OnManual) );
|
|
#else
|
|
c->AddItem( wxT("QuickHelp"), XXO("&Quick Help..."), FN(OnQuickHelp),
|
|
AlwaysEnabledFlag );
|
|
c->AddItem( wxT("Manual"), XXO("&Manual..."), FN(OnManual),
|
|
AlwaysEnabledFlag );
|
|
#endif
|
|
c->AddSeparator();
|
|
|
|
c->BeginSubMenu(_("&Diagnostics"));
|
|
c->AddItem( wxT("DeviceInfo"), XXO("Au&dio Device Info..."), FN(OnAudioDeviceInfo),
|
|
AudioIONotBusyFlag );
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
c->AddItem( wxT("MidiDeviceInfo"), XXO("&MIDI Device Info..."), FN(OnMidiDeviceInfo),
|
|
AudioIONotBusyFlag );
|
|
#endif
|
|
|
|
c->AddItem( wxT("Log"), XXO("Show &Log..."), FN(OnShowLog),
|
|
AlwaysEnabledFlag );
|
|
|
|
#if defined(EXPERIMENTAL_CRASH_REPORT)
|
|
c->AddItem( wxT("CrashReport"), XXO("&Generate Support Data..."),
|
|
FN(OnCrashReport), AlwaysEnabledFlag );
|
|
#endif
|
|
c->AddItem( wxT("CheckDeps"), XXO("Chec&k Dependencies..."), FN(OnCheckDependencies),
|
|
AudioIONotBusyFlag );
|
|
c->EndSubMenu();
|
|
|
|
#ifndef __WXMAC__
|
|
c->AddSeparator();
|
|
#endif
|
|
|
|
// DA: Does not fully support update checking.
|
|
#ifndef EXPERIMENTAL_DA
|
|
c->AddItem( wxT("Updates"), XXO("&Check for Updates..."), FN(OnCheckForUpdates),
|
|
AlwaysEnabledFlag );
|
|
#endif
|
|
c->AddItem( wxT("About"), XXO("&About Audacity..."), FN(OnAbout),
|
|
AlwaysEnabledFlag );
|
|
|
|
c->EndMenu();
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
|
|
project.SetMenuBar(menubar.release());
|
|
// Bug 143 workaround.
|
|
// The bug is in wxWidgets. For a menu that has scrollers, the
|
|
// scrollers have an ID of 0 (not wxID_NONE which is -3).
|
|
// Therefore wxWidgets attempts to find a help string. See
|
|
// wxFrameBase::ShowMenuHelp(int menuId)
|
|
// It finds a bogus automatic help string of "Recent &Files"
|
|
// from that submenu.
|
|
// So we set the help string for command with Id 0 to empty.
|
|
mRecentFilesMenu->GetParent()->SetHelpString( 0, "" );
|
|
}
|
|
|
|
|
|
|
|
mLastFlags = AlwaysEnabledFlag;
|
|
|
|
#if defined(__WXDEBUG__)
|
|
// c->CheckDups();
|
|
#endif
|
|
}
|
|
|
|
#undef XXO
|
|
|
|
|
|
|
|
void MenuCreator::PopulateMacrosMenu( CommandManager* c, CommandFlag flags )
|
|
{
|
|
wxArrayString names = MacroCommands::GetNames();
|
|
int i;
|
|
|
|
for (i = 0; i < (int)names.GetCount(); i++) {
|
|
wxString MacroID = ApplyMacroDialog::MacroIdOfName( names[i] );
|
|
c->AddItem( MacroID, names[i], false, FN(OnApplyMacroDirectly),
|
|
flags );
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/// The effects come from a plug in list
|
|
/// This code iterates through the list, adding effects into
|
|
/// the menu.
|
|
void MenuCreator::PopulateEffectsMenu(CommandManager* c,
|
|
EffectType type,
|
|
CommandFlag batchflags,
|
|
CommandFlag realflags)
|
|
{
|
|
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 );
|
|
std::sort( optplugs.begin(), optplugs.end(), comp2 );
|
|
|
|
AddEffectMenuItems(c, defplugs, batchflags, realflags, true);
|
|
|
|
if (defplugs.size() && optplugs.size())
|
|
{
|
|
c->AddSeparator();
|
|
}
|
|
|
|
AddEffectMenuItems(c, optplugs, batchflags, realflags, false);
|
|
|
|
return;
|
|
}
|
|
|
|
void MenuCreator::AddEffectMenuItems(CommandManager *c,
|
|
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)
|
|
{
|
|
bool bInSubmenu = !last.IsEmpty() && (groupNames.Count() > 1);
|
|
if( bInSubmenu)
|
|
c->BeginSubMenu(last);
|
|
|
|
AddEffectMenuItemGroup(c, groupNames, vHasDialog,
|
|
groupPlugs, groupFlags, isDefault);
|
|
|
|
if (bInSubmenu)
|
|
c->EndSubMenu();
|
|
|
|
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)
|
|
{
|
|
bool bInSubmenu = groupNames.Count() > 1;
|
|
if (bInSubmenu)
|
|
c->BeginSubMenu(current);
|
|
|
|
AddEffectMenuItemGroup(c, groupNames, vHasDialog, groupPlugs, groupFlags, isDefault);
|
|
|
|
if (bInSubmenu)
|
|
c->EndSubMenu();
|
|
}
|
|
}
|
|
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(c, groupNames, vHasDialog, groupPlugs, groupFlags, isDefault);
|
|
}
|
|
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void MenuCreator::AddEffectMenuItemGroup(CommandManager *c,
|
|
const wxArrayString & names,
|
|
const std::vector<bool> &vHasDialog,
|
|
const PluginIDList & plugs,
|
|
const std::vector<CommandFlag> & flags,
|
|
bool isDefault)
|
|
{
|
|
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;
|
|
}
|
|
|
|
int groupNdx = 0;
|
|
for (int i = 0; i < namesCnt; i++)
|
|
{
|
|
if (max > 0 && items == max)
|
|
{
|
|
int end = groupNdx + max;
|
|
if (end + 1 > groupCnt)
|
|
{
|
|
end = groupCnt;
|
|
}
|
|
c->BeginSubMenu(wxString::Format(_("Plug-in %d to %d"),
|
|
groupNdx + 1,
|
|
end));
|
|
}
|
|
|
|
if (i + 1 < namesCnt && names[i].IsSameAs(names[i + 1]))
|
|
{
|
|
wxString name = names[i];
|
|
c->BeginSubMenu(name);
|
|
while (i < namesCnt && names[i].IsSameAs(name))
|
|
{
|
|
const PluginDescriptor *plug = PluginManager::Get().GetPlugin(plugs[i]);
|
|
wxString item = plug->GetPath();
|
|
if( plug->GetPluginType() == PluginTypeEffect )
|
|
c->AddItem( item,
|
|
item,
|
|
item.Contains("..."),
|
|
FN(OnEffect),
|
|
flags[i],
|
|
CommandManager::Options{}
|
|
.IsEffect().Parameter( plugs[i] ) );
|
|
|
|
i++;
|
|
}
|
|
c->EndSubMenu();
|
|
i--;
|
|
}
|
|
else
|
|
{
|
|
const PluginDescriptor *plug = PluginManager::Get().GetPlugin(plugs[i]);
|
|
if( plug->GetPluginType() == PluginTypeEffect )
|
|
c->AddItem( names[i],
|
|
names[i],
|
|
vHasDialog[i],
|
|
FN(OnEffect),
|
|
flags[i],
|
|
CommandManager::Options{}
|
|
.IsEffect().Parameter( plugs[i] ) );
|
|
}
|
|
|
|
if (max > 0)
|
|
{
|
|
groupNdx++;
|
|
items--;
|
|
if (items == 0 || i + 1 == namesCnt)
|
|
{
|
|
c->EndSubMenu();
|
|
items = max;
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
#undef FN
|
|
|
|
void MenuCreator::CreateRecentFilesMenu(CommandManager *c)
|
|
{
|
|
// Recent Files and Recent Projects menus
|
|
|
|
#ifdef __WXMAC__
|
|
/* i18n-hint: This is the name of the menu item on Mac OS X only */
|
|
mRecentFilesMenu = c->BeginSubMenu(_("Open Recent"));
|
|
#else
|
|
/* i18n-hint: This is the name of the menu item on Windows and Linux */
|
|
mRecentFilesMenu = c->BeginSubMenu(_("Recent &Files"));
|
|
#endif
|
|
|
|
wxGetApp().GetRecentFiles()->UseMenu(mRecentFilesMenu);
|
|
wxGetApp().GetRecentFiles()->AddFilesToMenu(mRecentFilesMenu);
|
|
|
|
c->EndSubMenu();
|
|
|
|
}
|
|
|
|
// 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
|
|
|
|
// Allow FileHistory to remove its own menu
|
|
wxGetApp().GetRecentFiles()->RemoveMenu(mRecentFilesMenu);
|
|
|
|
// 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()))
|
|
GetMenuCommandHandler(*this).OnSelectSomething(*this);
|
|
}
|
|
|
|
// Stop playing or recording, if paused.
|
|
void AudacityProject::StopIfPaused()
|
|
{
|
|
auto flags = GetMenuManager(*this).GetUpdateFlags(*this);
|
|
if( flags & PausedFlag )
|
|
GetMenuCommandHandler(*this).OnStop(*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) {
|
|
mCommandManager.Enable(wxT("Copy"), true);
|
|
mCommandManager.Enable(wxT("Cut"), true);
|
|
}
|
|
#endif
|
|
|
|
MenuManager::ModifyToolbarMenus(project);
|
|
}
|
|
|
|
//
|
|
// Tool selection commands
|
|
//
|
|
|
|
/// Called by handlers that set tools.
|
|
void MenuCommandHandler::SetTool(AudacityProject &project, int tool)
|
|
{
|
|
ToolsToolBar *toolbar = project.GetToolsToolBar();
|
|
if (toolbar) {
|
|
toolbar->SetCurrentTool(tool);
|
|
project.GetTrackPanel()->Refresh(false);
|
|
}
|
|
}
|
|
|
|
/// Handler to set the select tool active
|
|
void MenuCommandHandler::OnSelectTool(const CommandContext &context)
|
|
{
|
|
SetTool(context.project, selectTool);
|
|
}
|
|
|
|
/// Handler to set the Zoom tool active
|
|
void MenuCommandHandler::OnZoomTool(const CommandContext &context)
|
|
{
|
|
SetTool(context.project, zoomTool);
|
|
}
|
|
|
|
/// Handler to set the Envelope tool active
|
|
void MenuCommandHandler::OnEnvelopeTool(const CommandContext &context)
|
|
{
|
|
SetTool(context.project, envelopeTool);
|
|
}
|
|
|
|
/// Handler to set the Time shift tool active
|
|
void MenuCommandHandler::OnTimeShiftTool(const CommandContext &context)
|
|
{
|
|
SetTool(context.project, slideTool);
|
|
}
|
|
|
|
void MenuCommandHandler::OnDrawTool(const CommandContext &context)
|
|
{
|
|
SetTool(context.project, drawTool);
|
|
}
|
|
|
|
void MenuCommandHandler::OnMultiTool(const CommandContext &context)
|
|
{
|
|
SetTool(context.project, multiTool);
|
|
}
|
|
|
|
|
|
void MenuCommandHandler::OnNextTool(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto toolbar = project.GetToolsToolBar();
|
|
auto trackPanel = project.GetTrackPanel();
|
|
|
|
if (toolbar) {
|
|
// Use GetDownTool() here since GetCurrentTool() can return a value that
|
|
// doesn't represent the real tool if the Multi-tool is being used.
|
|
toolbar->SetCurrentTool((toolbar->GetDownTool()+1)%numTools);
|
|
trackPanel->Refresh(false);
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::OnPrevTool(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto toolbar = project.GetToolsToolBar();
|
|
auto trackPanel = project.GetTrackPanel();
|
|
|
|
if (toolbar) {
|
|
// Use GetDownTool() here since GetCurrentTool() can return a value that
|
|
// doesn't represent the real tool if the Multi-tool is being used.
|
|
toolbar->SetCurrentTool((toolbar->GetDownTool()+(numTools-1))%numTools);
|
|
trackPanel->Refresh(false);
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Audio I/O Commands
|
|
//
|
|
|
|
// TODO: Should all these functions which involve
|
|
// the toolbar actually move into ControlToolBar?
|
|
|
|
/// MakeReadyToPlay stops whatever is currently playing
|
|
/// and pops the play button up. Then, if nothing is now
|
|
/// playing, it pushes the play button down and enables
|
|
/// the stop button.
|
|
bool MenuCommandHandler::MakeReadyToPlay(AudacityProject &project,
|
|
bool loop, bool cutpreview)
|
|
{
|
|
ControlToolBar *toolbar = project.GetControlToolBar();
|
|
wxCommandEvent evt;
|
|
|
|
// If this project is playing, stop playing
|
|
if (gAudioIO->IsStreamActive(project.GetAudioIOToken())) {
|
|
toolbar->SetPlay(false); //Pops
|
|
toolbar->SetStop(true); //Pushes stop down
|
|
toolbar->OnStop(evt);
|
|
|
|
::wxMilliSleep(100);
|
|
}
|
|
|
|
// If it didn't stop playing quickly, or if some other
|
|
// project is playing, return
|
|
if (gAudioIO->IsBusy())
|
|
return false;
|
|
|
|
ControlToolBar::PlayAppearance appearance =
|
|
cutpreview ? ControlToolBar::PlayAppearance::CutPreview
|
|
: loop ? ControlToolBar::PlayAppearance::Looped
|
|
: ControlToolBar::PlayAppearance::Straight;
|
|
toolbar->SetPlay(true, appearance);
|
|
toolbar->SetStop(false);
|
|
|
|
return true;
|
|
}
|
|
|
|
void MenuCommandHandler::OnPlayOneSecond(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
if( !MakeReadyToPlay(project) )
|
|
return;
|
|
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto controlToolBar = project.GetControlToolBar();
|
|
auto options = project.GetDefaultPlayOptions();
|
|
|
|
double pos = trackPanel->GetMostRecentXPos();
|
|
controlToolBar->PlayPlayRegion
|
|
(SelectedRegion(pos - 0.5, pos + 0.5), options,
|
|
PlayMode::oneSecondPlay);
|
|
}
|
|
|
|
|
|
/// The idea for this function (and first implementation)
|
|
/// was from Juhana Sadeharju. The function plays the
|
|
/// sound between the current mouse position and the
|
|
/// nearest selection boundary. This gives four possible
|
|
/// play regions depending on where the current mouse
|
|
/// position is relative to the left and right boundaries
|
|
/// of the selection region.
|
|
void MenuCommandHandler::OnPlayToSelection(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
|
|
if( !MakeReadyToPlay(project) )
|
|
return;
|
|
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto &viewInfo = project.GetViewInfo();
|
|
const auto &selectedRegion = viewInfo.selectedRegion;
|
|
|
|
double pos = trackPanel->GetMostRecentXPos();
|
|
|
|
double t0,t1;
|
|
// check region between pointer and the nearest selection edge
|
|
if (fabs(pos - selectedRegion.t0()) <
|
|
fabs(pos - selectedRegion.t1())) {
|
|
t0 = t1 = selectedRegion.t0();
|
|
} else {
|
|
t0 = t1 = selectedRegion.t1();
|
|
}
|
|
if( pos < t1)
|
|
t0=pos;
|
|
else
|
|
t1=pos;
|
|
|
|
// JKC: oneSecondPlay mode disables auto scrolling
|
|
// On balance I think we should always do this in this function
|
|
// since you are typically interested in the sound EXACTLY
|
|
// where the cursor is.
|
|
// TODO: have 'playing attributes' such as 'with_autoscroll'
|
|
// rather than modes, since that's how we're now using the modes.
|
|
|
|
// An alternative, commented out below, is to disable autoscroll
|
|
// only when playing a short region, less than or equal to a second.
|
|
// mLastPlayMode = ((t1-t0) > 1.0) ? normalPlay : oneSecondPlay;
|
|
|
|
auto controlToolBar = project.GetControlToolBar();
|
|
auto playOptions = project.GetDefaultPlayOptions();
|
|
|
|
controlToolBar->PlayPlayRegion
|
|
(SelectedRegion(t0, t1), playOptions, PlayMode::oneSecondPlay);
|
|
}
|
|
|
|
// The next 4 functions provide a limited version of the
|
|
// functionality of OnPlayToSelection() for keyboard users
|
|
|
|
void MenuCommandHandler::OnPlayBeforeSelectionStart(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
|
|
if( !MakeReadyToPlay(project) )
|
|
return;
|
|
|
|
auto &viewInfo = project.GetViewInfo();
|
|
const auto &selectedRegion = viewInfo.selectedRegion;
|
|
|
|
double t0 = selectedRegion.t0();
|
|
double beforeLen;
|
|
gPrefs->Read(wxT("/AudioIO/CutPreviewBeforeLen"), &beforeLen, 2.0);
|
|
|
|
auto controlToolBar = project.GetControlToolBar();
|
|
auto playOptions = project.GetDefaultPlayOptions();
|
|
|
|
controlToolBar->PlayPlayRegion(
|
|
SelectedRegion(t0 - beforeLen, t0), playOptions, PlayMode::oneSecondPlay);
|
|
}
|
|
|
|
void MenuCommandHandler::OnPlayAfterSelectionStart(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
|
|
if( !MakeReadyToPlay(project) )
|
|
return;
|
|
|
|
auto &viewInfo = project.GetViewInfo();
|
|
const auto &selectedRegion = viewInfo.selectedRegion;
|
|
|
|
double t0 = selectedRegion.t0();
|
|
double t1 = selectedRegion.t1();
|
|
double afterLen;
|
|
gPrefs->Read(wxT("/AudioIO/CutPreviewAfterLen"), &afterLen, 1.0);
|
|
|
|
auto controlToolBar = project.GetControlToolBar();
|
|
auto playOptions = project.GetDefaultPlayOptions();
|
|
|
|
if ( t1 - t0 > 0.0 && t1 - t0 < afterLen )
|
|
controlToolBar->PlayPlayRegion(
|
|
SelectedRegion(t0, t1), playOptions, PlayMode::oneSecondPlay);
|
|
else
|
|
controlToolBar->PlayPlayRegion(
|
|
SelectedRegion(t0, t0 + afterLen), playOptions, PlayMode::oneSecondPlay);
|
|
}
|
|
|
|
void MenuCommandHandler::OnPlayBeforeSelectionEnd(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
|
|
if( !MakeReadyToPlay(project) )
|
|
return;
|
|
|
|
auto &viewInfo = project.GetViewInfo();
|
|
const auto &selectedRegion = viewInfo.selectedRegion;
|
|
|
|
double t0 = selectedRegion.t0();
|
|
double t1 = selectedRegion.t1();
|
|
double beforeLen;
|
|
gPrefs->Read(wxT("/AudioIO/CutPreviewBeforeLen"), &beforeLen, 2.0);
|
|
|
|
auto controlToolBar = project.GetControlToolBar();
|
|
auto playOptions = project.GetDefaultPlayOptions();
|
|
|
|
if ( t1 - t0 > 0.0 && t1 - t0 < beforeLen )
|
|
controlToolBar->PlayPlayRegion(
|
|
SelectedRegion(t0, t1), playOptions, PlayMode::oneSecondPlay);
|
|
else
|
|
controlToolBar->PlayPlayRegion(
|
|
SelectedRegion(t1 - beforeLen, t1), playOptions, PlayMode::oneSecondPlay);
|
|
}
|
|
|
|
|
|
void MenuCommandHandler::OnPlayAfterSelectionEnd(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
|
|
if( !MakeReadyToPlay(project) )
|
|
return;
|
|
|
|
auto &viewInfo = project.GetViewInfo();
|
|
const auto &selectedRegion = viewInfo.selectedRegion;
|
|
|
|
double t1 = selectedRegion.t1();
|
|
double afterLen;
|
|
gPrefs->Read(wxT("/AudioIO/CutPreviewAfterLen"), &afterLen, 1.0);
|
|
|
|
auto controlToolBar = project.GetControlToolBar();
|
|
auto playOptions = project.GetDefaultPlayOptions();
|
|
|
|
controlToolBar->PlayPlayRegion(
|
|
SelectedRegion(t1, t1 + afterLen), playOptions, PlayMode::oneSecondPlay);
|
|
}
|
|
|
|
void MenuCommandHandler::OnPlayBeforeAndAfterSelectionStart
|
|
(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
|
|
if (!MakeReadyToPlay(project))
|
|
return;
|
|
|
|
auto &viewInfo = project.GetViewInfo();
|
|
const auto &selectedRegion = viewInfo.selectedRegion;
|
|
|
|
double t0 = selectedRegion.t0();
|
|
double t1 = selectedRegion.t1();
|
|
double beforeLen;
|
|
gPrefs->Read(wxT("/AudioIO/CutPreviewBeforeLen"), &beforeLen, 2.0);
|
|
double afterLen;
|
|
gPrefs->Read(wxT("/AudioIO/CutPreviewAfterLen"), &afterLen, 1.0);
|
|
|
|
auto controlToolBar = project.GetControlToolBar();
|
|
auto playOptions = project.GetDefaultPlayOptions();
|
|
|
|
if ( t1 - t0 > 0.0 && t1 - t0 < afterLen )
|
|
controlToolBar->PlayPlayRegion(
|
|
SelectedRegion(t0 - beforeLen, t1), playOptions,
|
|
PlayMode::oneSecondPlay);
|
|
else
|
|
controlToolBar->PlayPlayRegion(
|
|
SelectedRegion(t0 - beforeLen, t0 + afterLen), playOptions,
|
|
PlayMode::oneSecondPlay);
|
|
}
|
|
|
|
void MenuCommandHandler::OnPlayBeforeAndAfterSelectionEnd
|
|
(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
|
|
if (!MakeReadyToPlay(project))
|
|
return;
|
|
|
|
auto &viewInfo = project.GetViewInfo();
|
|
const auto &selectedRegion = viewInfo.selectedRegion;
|
|
|
|
double t0 = selectedRegion.t0();
|
|
double t1 = selectedRegion.t1();
|
|
double beforeLen;
|
|
gPrefs->Read(wxT("/AudioIO/CutPreviewBeforeLen"), &beforeLen, 2.0);
|
|
double afterLen;
|
|
gPrefs->Read(wxT("/AudioIO/CutPreviewAfterLen"), &afterLen, 1.0);
|
|
|
|
auto controlToolBar = project.GetControlToolBar();
|
|
auto playOptions = project.GetDefaultPlayOptions();
|
|
|
|
if ( t1 - t0 > 0.0 && t1 - t0 < beforeLen )
|
|
controlToolBar->PlayPlayRegion(
|
|
SelectedRegion(t0, t1 + afterLen), playOptions,
|
|
PlayMode::oneSecondPlay);
|
|
else
|
|
controlToolBar->PlayPlayRegion(
|
|
SelectedRegion(t1 - beforeLen, t1 + afterLen), playOptions,
|
|
PlayMode::oneSecondPlay);
|
|
}
|
|
|
|
|
|
void MenuCommandHandler::OnPlayLooped(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
|
|
if( !MakeReadyToPlay(project, true) )
|
|
return;
|
|
|
|
// Now play in a loop
|
|
// Will automatically set mLastPlayMode
|
|
auto controlToolBar = project.GetControlToolBar();
|
|
controlToolBar->PlayCurrentRegion(true);
|
|
}
|
|
|
|
void MenuCommandHandler::OnPlayCutPreview(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
|
|
if ( !MakeReadyToPlay(project, false, true) )
|
|
return;
|
|
|
|
// Play with cut preview
|
|
auto controlToolBar = project.GetControlToolBar();
|
|
controlToolBar->PlayCurrentRegion(false, true);
|
|
}
|
|
|
|
void MenuCommandHandler::OnPlayStop(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto toolbar = project.GetControlToolBar();
|
|
auto token = project.GetAudioIOToken();
|
|
|
|
//If this project is playing, stop playing, make sure everything is unpaused.
|
|
if (gAudioIO->IsStreamActive(token)) {
|
|
toolbar->SetPlay(false); //Pops
|
|
toolbar->SetStop(true); //Pushes stop down
|
|
toolbar->StopPlaying();
|
|
}
|
|
else if (gAudioIO->IsStreamActive()) {
|
|
//If this project isn't playing, but another one is, stop playing the old and start the NEW.
|
|
|
|
//find out which project we need;
|
|
AudacityProject* otherProject = NULL;
|
|
for(unsigned i=0; i<gAudacityProjects.size(); i++) {
|
|
if(gAudioIO->IsStreamActive(gAudacityProjects[i]->GetAudioIOToken())) {
|
|
otherProject=gAudacityProjects[i].get();
|
|
break;
|
|
}
|
|
}
|
|
|
|
//stop playing the other project
|
|
if(otherProject) {
|
|
ControlToolBar *otherToolbar = otherProject->GetControlToolBar();
|
|
otherToolbar->SetPlay(false); //Pops
|
|
otherToolbar->SetStop(true); //Pushes stop down
|
|
otherToolbar->StopPlaying();
|
|
}
|
|
|
|
//play the front project
|
|
if (!gAudioIO->IsBusy()) {
|
|
//update the playing area
|
|
project.TP_DisplaySelection();
|
|
//Otherwise, start playing (assuming audio I/O isn't busy)
|
|
//toolbar->SetPlay(true); // Not needed as done in PlayPlayRegion.
|
|
toolbar->SetStop(false);
|
|
|
|
// Will automatically set mLastPlayMode
|
|
toolbar->PlayCurrentRegion(false);
|
|
}
|
|
}
|
|
else if (!gAudioIO->IsBusy()) {
|
|
//Otherwise, start playing (assuming audio I/O isn't busy)
|
|
//toolbar->SetPlay(true); // Not needed as done in PlayPlayRegion.
|
|
toolbar->SetStop(false);
|
|
|
|
// Will automatically set mLastPlayMode
|
|
toolbar->PlayCurrentRegion(false);
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::OnStop(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
wxCommandEvent evt;
|
|
|
|
auto controlToolBar = project.GetControlToolBar();
|
|
controlToolBar->OnStop(evt);
|
|
}
|
|
|
|
void MenuCommandHandler::OnPause(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
wxCommandEvent evt;
|
|
|
|
auto controlToolBar = project.GetControlToolBar();
|
|
controlToolBar->OnPause(evt);
|
|
}
|
|
|
|
void MenuCommandHandler::OnRecord(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
wxCommandEvent evt;
|
|
evt.SetInt(2); // 0 is default, use 1 to set shift on, 2 to clear it
|
|
|
|
auto controlToolBar = project.GetControlToolBar();
|
|
controlToolBar->OnRecord(evt);
|
|
}
|
|
|
|
// If first choice is record same track 2nd choice is record NEW track
|
|
// and vice versa.
|
|
void MenuCommandHandler::OnRecord2ndChoice(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
wxCommandEvent evt;
|
|
evt.SetInt(1); // 0 is default, use 1 to set shift on, 2 to clear it
|
|
|
|
auto controlToolBar = project.GetControlToolBar();
|
|
controlToolBar->OnRecord(evt);
|
|
}
|
|
|
|
// The code for "OnPlayStopSelect" is simply the code of "OnPlayStop" and "OnStopSelect" merged.
|
|
void MenuCommandHandler::OnPlayStopSelect(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto toolbar = project.GetControlToolBar();
|
|
wxCommandEvent evt;
|
|
if (DoPlayStopSelect(project, false, false))
|
|
toolbar->OnStop(evt);
|
|
else if (!gAudioIO->IsBusy()) {
|
|
//Otherwise, start playing (assuming audio I/O isn't busy)
|
|
//toolbar->SetPlay(true); // Not needed as set in PlayPlayRegion()
|
|
toolbar->SetStop(false);
|
|
|
|
// Will automatically set mLastPlayMode
|
|
toolbar->PlayCurrentRegion(false);
|
|
}
|
|
}
|
|
|
|
bool MenuCommandHandler::DoPlayStopSelect
|
|
(AudacityProject &project, bool click, bool shift)
|
|
{
|
|
auto toolbar = project.GetControlToolBar();
|
|
auto &scrubber = project.GetScrubber();
|
|
auto token = project.GetAudioIOToken();
|
|
auto &viewInfo = project.GetViewInfo();
|
|
auto &selection = viewInfo.selectedRegion;
|
|
|
|
//If busy, stop playing, make sure everything is unpaused.
|
|
if (scrubber.HasMark() ||
|
|
gAudioIO->IsStreamActive(token)) {
|
|
toolbar->SetPlay(false); //Pops
|
|
toolbar->SetStop(true); //Pushes stop down
|
|
|
|
// change the selection
|
|
auto time = gAudioIO->GetStreamTime();
|
|
// Test WasSpeedPlaying(), not IsSpeedPlaying()
|
|
// as we could be stopped now.
|
|
if (click && scrubber.WasSpeedPlaying())
|
|
{
|
|
;// don't change the selection.
|
|
}
|
|
else if (shift && click) {
|
|
// Change the region selection, as if by shift-click at the play head
|
|
auto t0 = selection.t0(), t1 = selection.t1();
|
|
if (time < t0)
|
|
// Grow selection
|
|
t0 = time;
|
|
else if (time > t1)
|
|
// Grow selection
|
|
t1 = time;
|
|
else {
|
|
// Shrink selection, changing the nearer boundary
|
|
if (fabs(t0 - time) < fabs(t1 - time))
|
|
t0 = time;
|
|
else
|
|
t1 = time;
|
|
}
|
|
selection.setTimes(t0, t1);
|
|
}
|
|
else if (click){
|
|
// avoid a point at negative time.
|
|
time = wxMax( time, 0 );
|
|
// Set a point selection, as if by a click at the play head
|
|
selection.setTimes(time, time);
|
|
} else
|
|
// How stop and set cursor always worked
|
|
// -- change t0, collapsing to point only if t1 was greater
|
|
selection.setT0(time, false);
|
|
|
|
project.ModifyState(false); // without bWantsAutoSave
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void MenuCommandHandler::OnStopSelect(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto &viewInfo = project.GetViewInfo();
|
|
auto &selectedRegion = viewInfo.selectedRegion;
|
|
wxCommandEvent evt;
|
|
|
|
if (gAudioIO->IsStreamActive()) {
|
|
auto controlToolBar = project.GetControlToolBar();
|
|
selectedRegion.setT0(gAudioIO->GetStreamTime(), false);
|
|
controlToolBar->OnStop(evt);
|
|
project.ModifyState(false); // without bWantsAutoSave
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::OnToggleSoundActivated(const CommandContext &WXUNUSED(context) )
|
|
{
|
|
bool pause;
|
|
gPrefs->Read(wxT("/AudioIO/SoundActivatedRecord"), &pause, false);
|
|
gPrefs->Write(wxT("/AudioIO/SoundActivatedRecord"), !pause);
|
|
gPrefs->Flush();
|
|
MenuManager::ModifyAllProjectToolbarMenus();
|
|
}
|
|
|
|
void MenuCommandHandler::OnTogglePinnedHead(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
|
|
bool value = !TracksPrefs::GetPinnedHeadPreference();
|
|
TracksPrefs::SetPinnedHeadPreference(value, true);
|
|
MenuManager::ModifyAllProjectToolbarMenus();
|
|
|
|
// Change what happens in case transport is in progress right now
|
|
auto ctb = GetActiveProject()->GetControlToolBar();
|
|
if (ctb)
|
|
ctb->StartScrollingIfPreferred();
|
|
|
|
auto ruler = project.GetRulerPanel();
|
|
if (ruler)
|
|
// Update button image
|
|
ruler->UpdateButtonStates();
|
|
|
|
auto &scrubber = project.GetScrubber();
|
|
if (scrubber.HasMark())
|
|
scrubber.SetScrollScrubbing(value);
|
|
}
|
|
|
|
void MenuCommandHandler::OnTogglePlayRecording(const CommandContext &WXUNUSED(context) )
|
|
{
|
|
bool Duplex;
|
|
#ifdef EXPERIMENTAL_DA
|
|
gPrefs->Read(wxT("/AudioIO/Duplex"), &Duplex, false);
|
|
#else
|
|
gPrefs->Read(wxT("/AudioIO/Duplex"), &Duplex, true);
|
|
#endif
|
|
gPrefs->Write(wxT("/AudioIO/Duplex"), !Duplex);
|
|
gPrefs->Flush();
|
|
MenuManager::ModifyAllProjectToolbarMenus();
|
|
}
|
|
|
|
void MenuCommandHandler::OnToggleSWPlaythrough(const CommandContext &WXUNUSED(context) )
|
|
{
|
|
bool SWPlaythrough;
|
|
gPrefs->Read(wxT("/AudioIO/SWPlaythrough"), &SWPlaythrough, false);
|
|
gPrefs->Write(wxT("/AudioIO/SWPlaythrough"), !SWPlaythrough);
|
|
gPrefs->Flush();
|
|
MenuManager::ModifyAllProjectToolbarMenus();
|
|
}
|
|
|
|
#ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT
|
|
void AudacityProject::OnToggleAutomatedInputLevelAdjustment(
|
|
const CommandContext &WXUNUSED(context) )
|
|
{
|
|
bool AVEnabled;
|
|
gPrefs->Read(wxT("/AudioIO/AutomatedInputLevelAdjustment"), &AVEnabled, false);
|
|
gPrefs->Write(wxT("/AudioIO/AutomatedInputLevelAdjustment"), !AVEnabled);
|
|
gPrefs->Flush();
|
|
MenuManager::ModifyAllProjectToolbarMenus();
|
|
}
|
|
#endif
|
|
|
|
//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);
|
|
}
|
|
|
|
void MenuCommandHandler::OnSkipStart(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
wxCommandEvent evt;
|
|
|
|
auto controlToolBar = project.GetControlToolBar();
|
|
controlToolBar->OnRewind(evt);
|
|
project.ModifyState(false);
|
|
}
|
|
|
|
void MenuCommandHandler::OnSkipEnd(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
wxCommandEvent evt;
|
|
|
|
auto controlToolBar = project.GetControlToolBar();
|
|
controlToolBar->OnFF(evt);
|
|
project.ModifyState(false);
|
|
}
|
|
|
|
void MenuCommandHandler::OnSeekLeftShort(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
SeekLeftOrRight( project, DIRECTION_LEFT, CURSOR_MOVE );
|
|
}
|
|
|
|
void MenuCommandHandler::OnSeekRightShort(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
SeekLeftOrRight( project, DIRECTION_RIGHT, CURSOR_MOVE );
|
|
}
|
|
|
|
void MenuCommandHandler::OnSeekLeftLong(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
SeekLeftOrRight( project, DIRECTION_LEFT, SELECTION_EXTEND );
|
|
}
|
|
|
|
void MenuCommandHandler::OnSeekRightLong(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
SeekLeftOrRight( project, DIRECTION_RIGHT, SELECTION_EXTEND );
|
|
}
|
|
|
|
void MenuCommandHandler::OnSelToStart(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
project.Rewind(true);
|
|
project.ModifyState(false);
|
|
}
|
|
|
|
void MenuCommandHandler::OnSelToEnd(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
project.SkipEnd(true);
|
|
project.ModifyState(false);
|
|
}
|
|
|
|
void MenuCommandHandler::OnMoveToNextLabel(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
DoMoveToLabel(project, true);
|
|
}
|
|
|
|
void MenuCommandHandler::OnMoveToPrevLabel(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
DoMoveToLabel(project, false);
|
|
}
|
|
|
|
void MenuCommandHandler::DoMoveToLabel(AudacityProject &project, bool next)
|
|
{
|
|
auto tracks = project.GetTracks();
|
|
auto trackPanel = project.GetTrackPanel();
|
|
|
|
// Find the number of label tracks, and ptr to last track found
|
|
auto trackRange = tracks->Any<LabelTrack>();
|
|
auto lt = *trackRange.rbegin();
|
|
auto nLabelTrack = trackRange.size();
|
|
|
|
if (nLabelTrack == 0 ) {
|
|
trackPanel->MessageForScreenReader(_("no label track"));
|
|
}
|
|
else if (nLabelTrack > 1) { // find first label track, if any, starting at the focused track
|
|
lt =
|
|
*tracks->Find(trackPanel->GetFocusedTrack()).Filter<LabelTrack>();
|
|
if (!lt) {
|
|
trackPanel->MessageForScreenReader(_("no label track at or below focused track"));
|
|
}
|
|
}
|
|
|
|
// If there is a single label track, or there is a label track at or below the focused track
|
|
auto &selectedRegion = project.GetSelection();
|
|
if (lt) {
|
|
int i;
|
|
if (next)
|
|
i = lt->FindNextLabel(selectedRegion);
|
|
else
|
|
i = lt->FindPrevLabel(selectedRegion);
|
|
|
|
if (i >= 0) {
|
|
const LabelStruct* label = lt->GetLabel(i);
|
|
if (project.IsAudioActive()) {
|
|
OnPlayStop(project); // stop
|
|
selectedRegion = label->selectedRegion;
|
|
project.RedrawProject();
|
|
OnPlayStop(project); // play
|
|
}
|
|
else {
|
|
selectedRegion = label->selectedRegion;
|
|
trackPanel->ScrollIntoView(selectedRegion.t0());
|
|
project.RedrawProject();
|
|
}
|
|
|
|
wxString message;
|
|
message.Printf(wxT("%s %d of %d"), label->title, i + 1, lt->GetNumLabels() );
|
|
trackPanel->MessageForScreenReader(message);
|
|
}
|
|
else {
|
|
trackPanel->MessageForScreenReader(_("no labels in label track"));
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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::HandleListSelection
|
|
(AudacityProject &project, Track *t, bool shift, bool ctrl, bool modifyState)
|
|
{
|
|
auto tracks = project.GetTracks();
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto &selectionState = project.GetSelectionState();
|
|
auto &viewInfo = project.GetViewInfo();
|
|
auto isSyncLocked = project.IsSyncLocked();
|
|
auto mixerBoard = project.GetMixerBoard();
|
|
|
|
selectionState.HandleListSelection
|
|
( *tracks, viewInfo, *t,
|
|
shift, ctrl, isSyncLocked, mixerBoard );
|
|
|
|
if (! ctrl )
|
|
trackPanel->SetFocusedTrack(t);
|
|
project.Refresh(false);
|
|
if (modifyState)
|
|
project.ModifyState(true);
|
|
}
|
|
|
|
// If this returns true, then there was a key up, and nothing more to do,
|
|
// after this function has completed.
|
|
// (at most this function just does a ModifyState for the keyup)
|
|
bool MenuCommandHandler::OnlyHandleKeyUp( const CommandContext &context )
|
|
{
|
|
auto &project = context.project;
|
|
auto evt = context.pEvt;
|
|
bool bKeyUp = (evt) && evt->GetEventType() == wxEVT_KEY_UP;
|
|
|
|
if( project.IsAudioActive() )
|
|
return bKeyUp;
|
|
if( !bKeyUp )
|
|
return false;
|
|
|
|
project.ModifyState(false);
|
|
return true;
|
|
}
|
|
|
|
void MenuCommandHandler::OnCursorLeft(const CommandContext &context)
|
|
{
|
|
if( !OnlyHandleKeyUp( context ) )
|
|
SeekLeftOrRight( context.project, DIRECTION_LEFT, CURSOR_MOVE);
|
|
}
|
|
|
|
void MenuCommandHandler::OnCursorRight(const CommandContext &context)
|
|
{
|
|
if( !OnlyHandleKeyUp( context ) )
|
|
SeekLeftOrRight( context.project, DIRECTION_RIGHT, CURSOR_MOVE);
|
|
}
|
|
|
|
void MenuCommandHandler::OnCursorShortJumpLeft(const CommandContext &context)
|
|
{
|
|
DoCursorMove( context.project, -mSeekShort );
|
|
}
|
|
|
|
void MenuCommandHandler::OnCursorShortJumpRight(const CommandContext &context)
|
|
{
|
|
DoCursorMove( context.project, mSeekShort );
|
|
}
|
|
|
|
void MenuCommandHandler::OnCursorLongJumpLeft(const CommandContext &context)
|
|
{
|
|
DoCursorMove( context.project, -mSeekLong );
|
|
}
|
|
|
|
void MenuCommandHandler::OnCursorLongJumpRight(const CommandContext &context)
|
|
{
|
|
DoCursorMove( context.project, mSeekLong );
|
|
}
|
|
|
|
void MenuCommandHandler::OnSelSetExtendLeft(const CommandContext &context)
|
|
{
|
|
DoBoundaryMove( context.project, DIRECTION_LEFT);
|
|
}
|
|
|
|
void MenuCommandHandler::OnSelSetExtendRight(const CommandContext &context)
|
|
{
|
|
DoBoundaryMove( context.project, DIRECTION_RIGHT);
|
|
}
|
|
|
|
void MenuCommandHandler::OnSelExtendLeft(const CommandContext &context)
|
|
{
|
|
if( !OnlyHandleKeyUp( context ) )
|
|
SeekLeftOrRight( context.project, DIRECTION_LEFT, SELECTION_EXTEND );
|
|
}
|
|
|
|
void MenuCommandHandler::OnSelExtendRight(const CommandContext &context)
|
|
{
|
|
if( !OnlyHandleKeyUp( context ) )
|
|
SeekLeftOrRight( context.project, DIRECTION_RIGHT, SELECTION_EXTEND );
|
|
}
|
|
|
|
void MenuCommandHandler::OnSelContractLeft(const CommandContext &context)
|
|
{
|
|
if( !OnlyHandleKeyUp( context ) )
|
|
SeekLeftOrRight( context.project, DIRECTION_RIGHT, SELECTION_CONTRACT );
|
|
}
|
|
|
|
void MenuCommandHandler::OnSelContractRight(const CommandContext &context)
|
|
{
|
|
if( !OnlyHandleKeyUp( context ) )
|
|
SeekLeftOrRight( context.project, DIRECTION_LEFT, SELECTION_CONTRACT );
|
|
}
|
|
|
|
#include "tracks/ui/TimeShiftHandle.h"
|
|
|
|
// This function returns the amount moved. Possibly 0.0.
|
|
double MenuCommandHandler::OnClipMove
|
|
( ViewInfo &viewInfo, Track *track,
|
|
TrackList &trackList, bool syncLocked, bool right )
|
|
{
|
|
auto &selectedRegion = viewInfo.selectedRegion;
|
|
|
|
// just dealing with clips in wave tracks for the moment. Note tracks??
|
|
if (track) return track->TypeSwitch<double>( [&]( WaveTrack *wt ) {
|
|
ClipMoveState state;
|
|
|
|
auto t0 = selectedRegion.t0();
|
|
|
|
// Find the first channel that has a clip at time t0
|
|
for (auto channel : TrackList::Channels(wt) ) {
|
|
if( nullptr != (state.capturedClip = channel->GetClipAtTime( t0 )) ) {
|
|
wt = channel;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (state.capturedClip == nullptr)
|
|
return 0.0;
|
|
|
|
state.capturedClipIsSelection =
|
|
track->GetSelected() && !selectedRegion.isPoint();
|
|
state.trackExclusions.clear();
|
|
|
|
TimeShiftHandle::CreateListOfCapturedClips
|
|
( state, viewInfo, *track, trackList, syncLocked, t0 );
|
|
|
|
auto desiredT0 = viewInfo.OffsetTimeByPixels( t0, ( right ? 1 : -1 ) );
|
|
auto desiredSlideAmount = desiredT0 - t0;
|
|
|
|
// set it to a sample point, and minimum of 1 sample point
|
|
if (!right)
|
|
desiredSlideAmount *= -1;
|
|
double nSamples = rint(wt->GetRate() * desiredSlideAmount);
|
|
nSamples = std::max(nSamples, 1.0);
|
|
desiredSlideAmount = nSamples / wt->GetRate();
|
|
if (!right)
|
|
desiredSlideAmount *= -1;
|
|
|
|
state.hSlideAmount = desiredSlideAmount;
|
|
TimeShiftHandle::DoSlideHorizontal( state, trackList, *track );
|
|
|
|
// update t0 and t1. There is the possibility that the updated
|
|
// t0 may no longer be within the clip due to rounding errors,
|
|
// so t0 is adjusted so that it is.
|
|
double newT0 = t0 + state.hSlideAmount;
|
|
if (newT0 < state.capturedClip->GetStartTime())
|
|
newT0 = state.capturedClip->GetStartTime();
|
|
if (newT0 > state.capturedClip->GetEndTime())
|
|
newT0 = state.capturedClip->GetEndTime();
|
|
double diff = selectedRegion.duration();
|
|
selectedRegion.setTimes(newT0, newT0 + diff);
|
|
|
|
return state.hSlideAmount;
|
|
} );
|
|
return 0.0;
|
|
}
|
|
|
|
void MenuCommandHandler::DoClipLeftOrRight
|
|
(AudacityProject &project, bool right, bool keyUp )
|
|
{
|
|
auto &undoManager = *project.GetUndoManager();
|
|
|
|
if (keyUp) {
|
|
undoManager.StopConsolidating();
|
|
return;
|
|
}
|
|
|
|
auto &panel = *project.GetTrackPanel();
|
|
auto &viewInfo = project.GetViewInfo();
|
|
auto &selectedRegion = viewInfo.selectedRegion;
|
|
auto tracks = project.GetTracks();
|
|
auto isSyncLocked = project.IsSyncLocked();
|
|
|
|
auto amount = OnClipMove
|
|
( viewInfo, panel.GetFocusedTrack(),
|
|
*tracks, isSyncLocked, right );
|
|
|
|
panel.ScrollIntoView(selectedRegion.t0());
|
|
panel.Refresh(false);
|
|
|
|
if (amount != 0.0) {
|
|
wxString message = right? _("Time shifted clips to the right") :
|
|
_("Time shifted clips to the left");
|
|
|
|
// The following use of the UndoPush flags is so that both a single
|
|
// keypress (keydown, then keyup), and holding down a key
|
|
// (multiple keydowns followed by a keyup) result in a single
|
|
// entry in Audacity's history dialog.
|
|
project.PushState(message, _("Time-Shift"), UndoPush::CONSOLIDATE);
|
|
}
|
|
|
|
if ( amount == 0.0 )
|
|
panel.MessageForScreenReader( _("clip not moved"));
|
|
}
|
|
|
|
void MenuCommandHandler::OnClipLeft(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto evt = context.pEvt;
|
|
if (evt)
|
|
DoClipLeftOrRight( project, false, evt->GetEventType() == wxEVT_KEY_UP );
|
|
else { // called from menu, so simulate keydown and keyup
|
|
DoClipLeftOrRight( project, false, false );
|
|
DoClipLeftOrRight( project, false, true );
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::OnClipRight(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto evt = context.pEvt;
|
|
if (evt)
|
|
DoClipLeftOrRight( project, true, evt->GetEventType() == wxEVT_KEY_UP );
|
|
else { // called from menu, so simulate keydown and keyup
|
|
DoClipLeftOrRight( project, true, false );
|
|
DoClipLeftOrRight( project, true, true );
|
|
}
|
|
}
|
|
|
|
//this pops up a dialog which allows the left selection to be set.
|
|
//If playing/recording is happening, it sets the left selection at
|
|
//the current play position.
|
|
void MenuCommandHandler::OnSetLeftSelection(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto token = project.GetAudioIOToken();
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
auto trackPanel = project.GetTrackPanel();
|
|
|
|
bool bSelChanged = false;
|
|
if ((token > 0) && gAudioIO->IsStreamActive(token))
|
|
{
|
|
double indicator = gAudioIO->GetStreamTime();
|
|
selectedRegion.setT0(indicator, false);
|
|
bSelChanged = true;
|
|
}
|
|
else
|
|
{
|
|
auto fmt = project.GetSelectionFormat();
|
|
auto rate = project.GetRate();
|
|
|
|
TimeDialog dlg(&project, _("Set Left Selection Boundary"),
|
|
fmt, rate, selectedRegion.t0(), _("Position"));
|
|
|
|
if (wxID_OK == dlg.ShowModal())
|
|
{
|
|
//Get the value from the dialog
|
|
selectedRegion.setT0(
|
|
std::max(0.0, dlg.GetTimeValue()), false);
|
|
bSelChanged = true;
|
|
}
|
|
}
|
|
|
|
if (bSelChanged)
|
|
{
|
|
project.ModifyState(false);
|
|
trackPanel->Refresh(false);
|
|
}
|
|
}
|
|
|
|
|
|
void MenuCommandHandler::OnSetRightSelection(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto token = project.GetAudioIOToken();
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
auto trackPanel = project.GetTrackPanel();
|
|
|
|
bool bSelChanged = false;
|
|
if ((token > 0) && gAudioIO->IsStreamActive(token))
|
|
{
|
|
double indicator = gAudioIO->GetStreamTime();
|
|
selectedRegion.setT1(indicator, false);
|
|
bSelChanged = true;
|
|
}
|
|
else
|
|
{
|
|
auto fmt = project.GetSelectionFormat();
|
|
auto rate = project.GetRate();
|
|
|
|
TimeDialog dlg(&project, _("Set Right Selection Boundary"),
|
|
fmt, rate, selectedRegion.t1(), _("Position"));
|
|
|
|
if (wxID_OK == dlg.ShowModal())
|
|
{
|
|
//Get the value from the dialog
|
|
selectedRegion.setT1(
|
|
std::max(0.0, dlg.GetTimeValue()), false);
|
|
bSelChanged = true;
|
|
}
|
|
}
|
|
|
|
if (bSelChanged)
|
|
{
|
|
project.ModifyState(false);
|
|
trackPanel->Refresh(false);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::OnPlayAtSpeed(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tb = project.GetTranscriptionToolBar();
|
|
|
|
if (tb) {
|
|
tb->PlayAtSpeed(false, false);
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::OnPlayAtSpeedLooped(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tb = project.GetTranscriptionToolBar();
|
|
|
|
if (tb) {
|
|
tb->PlayAtSpeed(true, false);
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::OnPlayAtSpeedCutPreview(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tb = project.GetTranscriptionToolBar();
|
|
|
|
if (tb) {
|
|
tb->PlayAtSpeed(false, true);
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::OnSetPlaySpeed(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tb = project.GetTranscriptionToolBar();
|
|
|
|
if (tb) {
|
|
tb->ShowPlaySpeedDialog();
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::OnPlaySpeedInc(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tb = project.GetTranscriptionToolBar();
|
|
|
|
if (tb) {
|
|
tb->AdjustPlaySpeed(0.1f);
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::OnPlaySpeedDec(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tb = project.GetTranscriptionToolBar();
|
|
|
|
if (tb) {
|
|
tb->AdjustPlaySpeed(-0.1f);
|
|
}
|
|
}
|
|
|
|
double MenuCommandHandler::NearestZeroCrossing
|
|
(AudacityProject &project, double t0)
|
|
{
|
|
auto rate = project.GetRate();
|
|
auto tracks = project.GetTracks();
|
|
|
|
// Window is 1/100th of a second.
|
|
auto windowSize = size_t(std::max(1.0, rate / 100));
|
|
Floats dist{ windowSize, true };
|
|
|
|
int nTracks = 0;
|
|
for (auto one : tracks->Selected< const WaveTrack >()) {
|
|
auto oneWindowSize = size_t(std::max(1.0, one->GetRate() / 100));
|
|
Floats oneDist{ oneWindowSize };
|
|
auto s = one->TimeToLongSamples(t0);
|
|
// fillTwo to ensure that missing values are treated as 2, and hence do not
|
|
// get used as zero crossings.
|
|
one->Get((samplePtr)oneDist.get(), floatSample,
|
|
s - (int)oneWindowSize/2, oneWindowSize, fillTwo);
|
|
|
|
|
|
// Looking for actual crossings.
|
|
double prev = 2.0;
|
|
for(size_t i=0; i<oneWindowSize; i++){
|
|
float fDist = fabs( oneDist[i]); // score is absolute value
|
|
if( prev * oneDist[i] > 0 ) // both same sign? No good.
|
|
fDist = fDist + 0.4; // No good if same sign.
|
|
else if( prev > 0.0 )
|
|
fDist = fDist + 0.1; // medium penalty for downward crossing.
|
|
prev = oneDist[i];
|
|
oneDist[i] = fDist;
|
|
}
|
|
|
|
// TODO: The mixed rate zero crossing code is broken,
|
|
// if oneWindowSize > windowSize we'll miss out some
|
|
// samples - so they will still be zero, so we'll use them.
|
|
for(size_t i = 0; i < windowSize; i++) {
|
|
size_t j;
|
|
if (windowSize != oneWindowSize)
|
|
j = i * (oneWindowSize-1) / (windowSize-1);
|
|
else
|
|
j = i;
|
|
|
|
dist[i] += oneDist[j];
|
|
// Apply a small penalty for distance from the original endpoint
|
|
// We'll always prefer an upward
|
|
dist[i] += 0.1 * (abs(int(i) - int(windowSize/2))) / float(windowSize/2);
|
|
}
|
|
nTracks++;
|
|
}
|
|
|
|
// Find minimum
|
|
int argmin = 0;
|
|
float min = 3.0;
|
|
for(size_t i=0; i<windowSize; i++) {
|
|
if (dist[i] < min) {
|
|
argmin = i;
|
|
min = dist[i];
|
|
}
|
|
}
|
|
|
|
// If we're worse than 0.2 on average, on one track, then no good.
|
|
if(( nTracks == 1 ) && ( min > (0.2*nTracks) ))
|
|
return t0;
|
|
// If we're worse than 0.6 on average, on multi-track, then no good.
|
|
if(( nTracks > 1 ) && ( min > (0.6*nTracks) ))
|
|
return t0;
|
|
|
|
return t0 + (argmin - (int)windowSize/2) / rate;
|
|
}
|
|
|
|
void MenuCommandHandler::OnZeroCrossing(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
auto trackPanel = project.GetTrackPanel();
|
|
|
|
const double t0 = NearestZeroCrossing(project, selectedRegion.t0());
|
|
if (selectedRegion.isPoint())
|
|
selectedRegion.setTimes(t0, t0);
|
|
else {
|
|
const double t1 = NearestZeroCrossing(project, selectedRegion.t1());
|
|
// Empty selection is generally not much use, so do not make it if empty.
|
|
if( fabs( t1 - t0 ) * project.GetRate() > 1.5 )
|
|
selectedRegion.setTimes(t0, t1);
|
|
}
|
|
|
|
project.ModifyState(false);
|
|
|
|
trackPanel->Refresh(false);
|
|
}
|
|
|
|
|
|
/// 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)
|
|
{
|
|
OnStop(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)
|
|
{
|
|
OnStop(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))
|
|
OnZoomFit(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::OnNew(const CommandContext &WXUNUSED(context) )
|
|
{
|
|
CreateNewAudacityProject();
|
|
}
|
|
|
|
void MenuCommandHandler::OnOpen(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
AudacityProject::OpenFiles(&project);
|
|
}
|
|
|
|
// JKC: This is like OnClose, except it emptys the project in place,
|
|
// rather than createing a new empty project (with new toolbars etc).
|
|
// It does not test for unsaved changes.
|
|
// It is not in the menus by default. Its main purpose is/was for
|
|
// developers checking functionality of ResetProjectToEmpty().
|
|
void MenuCommandHandler::OnProjectReset(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
project.ResetProjectToEmpty();
|
|
}
|
|
|
|
void MenuCommandHandler::OnClose(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
project.SetMenuClose(true);
|
|
project.Close();
|
|
}
|
|
|
|
void MenuCommandHandler::OnSave(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
project.Save();
|
|
}
|
|
|
|
void MenuCommandHandler::OnSaveAs(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
project.SaveAs();
|
|
}
|
|
|
|
void MenuCommandHandler::OnSaveCopy(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
project.SaveAs(true, true);
|
|
}
|
|
|
|
#ifdef USE_LIBVORBIS
|
|
void MenuCommandHandler::OnSaveCompressed(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
project.SaveAs(true);
|
|
}
|
|
#endif
|
|
|
|
void MenuCommandHandler::OnCheckDependencies(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
::ShowDependencyDialogIfNeeded(&project, false);
|
|
}
|
|
|
|
void MenuCommandHandler::OnExit(const CommandContext &WXUNUSED(context) )
|
|
{
|
|
QuitAudacity();
|
|
}
|
|
|
|
void MenuCommandHandler::OnExportLabels(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tracks = project.GetTracks();
|
|
|
|
/* i18n-hint: filename containing exported text from label tracks */
|
|
wxString fName = _("labels.txt");
|
|
auto trackRange = tracks->Any<const LabelTrack>();
|
|
auto numLabelTracks = trackRange.size();
|
|
|
|
if (numLabelTracks == 0) {
|
|
AudacityMessageBox(_("There are no label tracks to export."));
|
|
return;
|
|
}
|
|
else
|
|
fName = (*trackRange.rbegin())->GetName();
|
|
|
|
fName = FileNames::SelectFile(FileNames::Operation::Export,
|
|
_("Export Labels As:"),
|
|
wxEmptyString,
|
|
fName,
|
|
wxT("txt"),
|
|
wxT("*.txt"),
|
|
wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxRESIZE_BORDER,
|
|
&project);
|
|
|
|
if (fName == wxT(""))
|
|
return;
|
|
|
|
// Move existing files out of the way. Otherwise wxTextFile will
|
|
// append to (rather than replace) the current file.
|
|
|
|
if (wxFileExists(fName)) {
|
|
#ifdef __WXGTK__
|
|
wxString safetyFileName = fName + wxT("~");
|
|
#else
|
|
wxString safetyFileName = fName + wxT(".bak");
|
|
#endif
|
|
|
|
if (wxFileExists(safetyFileName))
|
|
wxRemoveFile(safetyFileName);
|
|
|
|
wxRename(fName, safetyFileName);
|
|
}
|
|
|
|
wxTextFile f(fName);
|
|
f.Create();
|
|
f.Open();
|
|
if (!f.IsOpened()) {
|
|
AudacityMessageBox( wxString::Format(
|
|
_("Couldn't write to file: %s"), fName ) );
|
|
return;
|
|
}
|
|
|
|
for (auto lt : trackRange)
|
|
lt->Export(f);
|
|
|
|
f.Write();
|
|
f.Close();
|
|
}
|
|
|
|
|
|
#ifdef USE_MIDI
|
|
void MenuCommandHandler::OnExportMIDI(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tracks = project.GetTracks();
|
|
|
|
// Make sure that there is
|
|
// exactly one NoteTrack selected.
|
|
const auto range = tracks->Selected< const NoteTrack >();
|
|
const auto numNoteTracksSelected = range.size();
|
|
|
|
if(numNoteTracksSelected > 1) {
|
|
AudacityMessageBox(_(
|
|
"Please select only one Note Track at a time."));
|
|
return;
|
|
}
|
|
else if(numNoteTracksSelected < 1) {
|
|
AudacityMessageBox(_(
|
|
"Please select a Note Track."));
|
|
return;
|
|
}
|
|
|
|
wxASSERT(numNoteTracksSelected);
|
|
if (!numNoteTracksSelected)
|
|
return;
|
|
|
|
const auto nt = *range.begin();
|
|
|
|
while(true) {
|
|
|
|
wxString fName = wxT("");
|
|
|
|
fName = FileNames::SelectFile(FileNames::Operation::Export,
|
|
_("Export MIDI As:"),
|
|
wxEmptyString,
|
|
fName,
|
|
wxT(".mid|.gro"),
|
|
_("MIDI file (*.mid)|*.mid|Allegro file (*.gro)|*.gro"),
|
|
wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxRESIZE_BORDER,
|
|
&project);
|
|
|
|
if (fName == wxT(""))
|
|
return;
|
|
|
|
if(!fName.Contains(wxT("."))) {
|
|
fName = fName + wxT(".mid");
|
|
}
|
|
|
|
// Move existing files out of the way. Otherwise wxTextFile will
|
|
// append to (rather than replace) the current file.
|
|
|
|
if (wxFileExists(fName)) {
|
|
#ifdef __WXGTK__
|
|
wxString safetyFileName = fName + wxT("~");
|
|
#else
|
|
wxString safetyFileName = fName + wxT(".bak");
|
|
#endif
|
|
|
|
if (wxFileExists(safetyFileName))
|
|
wxRemoveFile(safetyFileName);
|
|
|
|
wxRename(fName, safetyFileName);
|
|
}
|
|
|
|
if(fName.EndsWith(wxT(".mid")) || fName.EndsWith(wxT(".midi"))) {
|
|
nt->ExportMIDI(fName);
|
|
} else if(fName.EndsWith(wxT(".gro"))) {
|
|
nt->ExportAllegro(fName);
|
|
} else {
|
|
wxString msg = _("You have selected a filename with an unrecognized file extension.\nDo you want to continue?");
|
|
wxString title = _("Export MIDI");
|
|
int id = AudacityMessageBox(msg, title, wxYES_NO);
|
|
if (id == wxNO) {
|
|
continue;
|
|
} else if (id == wxYES) {
|
|
nt->ExportMIDI(fName);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
#endif // USE_MIDI
|
|
|
|
|
|
void MenuCommandHandler::DoExport
|
|
(AudacityProject &project, const wxString & Format )
|
|
{
|
|
auto tracks = project.GetTracks();
|
|
|
|
Exporter e;
|
|
|
|
wxGetApp().SetMissingAliasedFileWarningShouldShow(true);
|
|
double t0 = 0.0;
|
|
double t1 = tracks->GetEndTime();
|
|
|
|
// Prompt for file name and/or extension?
|
|
bool bPromptingRequired =
|
|
(project.mBatchMode == 0) || project.GetFileName().IsEmpty() ||
|
|
Format.IsEmpty();
|
|
wxString filename;
|
|
|
|
if (!bPromptingRequired) {
|
|
|
|
// We're in batch mode, and we have an mFileName and Format.
|
|
wxString extension = ".";
|
|
extension += Format;
|
|
extension.MakeLower();
|
|
|
|
filename = MacroCommands::BuildCleanFileName(project.GetFileName(), extension);
|
|
|
|
// Bug 1854, No warning of file overwrite (when export is called from Macros).
|
|
int counter = 0;
|
|
bPromptingRequired = wxFileExists(filename);
|
|
|
|
// We'll try alternative names to avoid overwriting.
|
|
while ( bPromptingRequired && counter < 100 ) {
|
|
counter++;
|
|
wxString number;
|
|
number.Printf("%03i", counter);
|
|
// So now the name has a number in it too.
|
|
filename =
|
|
MacroCommands::BuildCleanFileName(project.GetFileName(), number + extension);
|
|
bPromptingRequired = wxFileExists(filename);
|
|
}
|
|
// If we've run out of alternative names, we will fall back to prompting - even if in a macro.
|
|
}
|
|
|
|
|
|
if (bPromptingRequired)
|
|
{
|
|
// Do export with prompting.
|
|
e.SetDefaultFormat(Format);
|
|
e.Process(&project, false, t0, t1);
|
|
}
|
|
else
|
|
{
|
|
wxGetApp().AddFileToHistory(filename);
|
|
// We're in batch mode, the file does not exist already.
|
|
// We really can proceed without prompting.
|
|
int nChannels = MacroCommands::IsMono() ? 1 : 2;
|
|
e.Process(
|
|
&project, // AudacityProject
|
|
nChannels, // numChannels,
|
|
Format, // type,
|
|
filename, // filename,
|
|
false, // selectedOnly,
|
|
t0, // t0
|
|
t1 // t1
|
|
);
|
|
}
|
|
|
|
}
|
|
|
|
void MenuCommandHandler::OnExportAudio(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
DoExport(project, "");
|
|
}
|
|
|
|
void MenuCommandHandler::OnExportMp3(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
DoExport(project, "MP3");
|
|
}
|
|
|
|
void MenuCommandHandler::OnExportWav(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
DoExport(project, "WAV");
|
|
}
|
|
|
|
void MenuCommandHandler::OnExportOgg(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
DoExport(project, "OGG");
|
|
}
|
|
|
|
|
|
void MenuCommandHandler::OnExportSelection(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
Exporter e;
|
|
|
|
wxGetApp().SetMissingAliasedFileWarningShouldShow(true);
|
|
e.SetFileDialogTitle( _("Export Selected Audio") );
|
|
e.Process(&project, true, selectedRegion.t0(),
|
|
selectedRegion.t1());
|
|
}
|
|
|
|
void MenuCommandHandler::OnExportMultiple(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
ExportMultiple em(&project);
|
|
|
|
wxGetApp().SetMissingAliasedFileWarningShouldShow(true);
|
|
em.ShowModal();
|
|
}
|
|
|
|
void MenuCommandHandler::OnPreferences(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
|
|
GlobalPrefsDialog dialog(&project /* parent */ );
|
|
|
|
if( ScreenshotCommand::MayCapture( &dialog ) )
|
|
return;
|
|
|
|
if (!dialog.ShowModal()) {
|
|
// Canceled
|
|
return;
|
|
}
|
|
|
|
// LL: Moved from PrefsDialog since wxWidgets on OSX can't deal with
|
|
// rebuilding the menus while the PrefsDialog is still in the modal
|
|
// state.
|
|
for (size_t i = 0; i < gAudacityProjects.size(); i++) {
|
|
AudacityProject *p = gAudacityProjects[i].get();
|
|
|
|
GetMenuManager(*p).RebuildMenuBar(*p);
|
|
p->RebuildOtherMenus();
|
|
// TODO: The comment below suggests this workaround is obsolete.
|
|
#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
|
|
}
|
|
}
|
|
|
|
#include "./prefs/SpectrogramSettings.h"
|
|
#include "./prefs/WaveformSettings.h"
|
|
void MenuCommandHandler::OnReloadPreferences(const CommandContext &context )
|
|
{
|
|
auto &project = context.project;
|
|
|
|
{
|
|
SpectrogramSettings::defaults().LoadPrefs();
|
|
WaveformSettings::defaults().LoadPrefs();
|
|
|
|
GlobalPrefsDialog dialog(&project /* parent */ );
|
|
wxCommandEvent Evt;
|
|
//dialog.Show();
|
|
dialog.OnOK(Evt);
|
|
}
|
|
|
|
// LL: Moved from PrefsDialog since wxWidgets on OSX can't deal with
|
|
// rebuilding the menus while the PrefsDialog is still in the modal
|
|
// state.
|
|
for (size_t i = 0; i < gAudacityProjects.size(); i++) {
|
|
AudacityProject *p = gAudacityProjects[i].get();
|
|
|
|
GetMenuManager(*p).RebuildMenuBar(*p);
|
|
p->RebuildOtherMenus();
|
|
// TODO: The comment below suggests this workaround is obsolete.
|
|
#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::OnPageSetup(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
HandlePageSetup(&project);
|
|
}
|
|
|
|
void MenuCommandHandler::OnPrint(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto name = project.GetName();
|
|
auto tracks = project.GetTracks();
|
|
HandlePrint(&project, name, tracks);
|
|
}
|
|
|
|
//
|
|
// Edit Menu
|
|
//
|
|
|
|
void MenuCommandHandler::OnUndo(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto &undoManager = *project.GetUndoManager();
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
auto mixerBoard = project.GetMixerBoard();
|
|
auto historyWindow = project.GetHistoryWindow();
|
|
|
|
if (!project.UndoAvailable()) {
|
|
AudacityMessageBox(_("Nothing to undo"));
|
|
return;
|
|
}
|
|
|
|
// can't undo while dragging
|
|
if (trackPanel->IsMouseCaptured()) {
|
|
return;
|
|
}
|
|
|
|
const UndoState &state = undoManager.Undo(&selectedRegion);
|
|
project.PopState(state);
|
|
|
|
trackPanel->EnsureVisible(trackPanel->GetFirstSelectedTrack());
|
|
|
|
project.RedrawProject();
|
|
|
|
if (historyWindow)
|
|
historyWindow->UpdateDisplay();
|
|
|
|
if (mixerBoard)
|
|
// Mixer board may need to change for selection state and pan/gain
|
|
mixerBoard->Refresh();
|
|
|
|
MenuManager::ModifyUndoMenuItems(project);
|
|
}
|
|
|
|
void MenuCommandHandler::OnRedo(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto &undoManager = *project.GetUndoManager();
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
auto mixerBoard = project.GetMixerBoard();
|
|
auto historyWindow = project.GetHistoryWindow();
|
|
|
|
if (!project.RedoAvailable()) {
|
|
AudacityMessageBox(_("Nothing to redo"));
|
|
return;
|
|
}
|
|
// Can't redo whilst dragging
|
|
if (trackPanel->IsMouseCaptured()) {
|
|
return;
|
|
}
|
|
|
|
const UndoState &state = undoManager.Redo(&selectedRegion);
|
|
project.PopState(state);
|
|
|
|
trackPanel->EnsureVisible(trackPanel->GetFirstSelectedTrack());
|
|
|
|
project.RedrawProject();
|
|
|
|
if (historyWindow)
|
|
historyWindow->UpdateDisplay();
|
|
|
|
if (mixerBoard)
|
|
// Mixer board may need to change for selection state and pan/gain
|
|
mixerBoard->Refresh();
|
|
|
|
MenuManager::ModifyUndoMenuItems(project);
|
|
}
|
|
|
|
void MenuCommandHandler::FinishCopy
|
|
(const Track *n, Track::Holder &&dest, TrackList &list)
|
|
{
|
|
Track::FinishCopy( n, dest.get() );
|
|
if (dest)
|
|
list.Add(std::move(dest));
|
|
}
|
|
|
|
void MenuCommandHandler::OnCut(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tracks = project.GetTracks();
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
auto ruler = project.GetRulerPanel();
|
|
auto historyWindow = project.GetHistoryWindow();
|
|
|
|
// This doesn't handle cutting labels, it handles
|
|
// cutting the _text_ inside of labels, i.e. if you're
|
|
// in the middle of editing the label text and select "Cut".
|
|
|
|
for (auto lt : tracks->Selected< LabelTrack >()) {
|
|
if (lt->CutSelectedText()) {
|
|
trackPanel->Refresh(false);
|
|
return;
|
|
}
|
|
}
|
|
|
|
AudacityProject::ClearClipboard();
|
|
|
|
auto pNewClipboard = TrackList::Create();
|
|
auto &newClipboard = *pNewClipboard;
|
|
|
|
tracks->Selected().Visit(
|
|
#if defined(USE_MIDI)
|
|
[&](NoteTrack *n) {
|
|
// Since portsmf has a built-in cut operator, we use that instead
|
|
auto dest = n->Cut(selectedRegion.t0(),
|
|
selectedRegion.t1());
|
|
FinishCopy(n, std::move(dest), newClipboard);
|
|
},
|
|
#endif
|
|
[&](Track *n) {
|
|
auto dest = n->Copy(selectedRegion.t0(),
|
|
selectedRegion.t1());
|
|
FinishCopy(n, std::move(dest), newClipboard);
|
|
}
|
|
);
|
|
|
|
// Survived possibility of exceptions. Commit changes to the clipboard now.
|
|
newClipboard.Swap(*AudacityProject::msClipboard);
|
|
|
|
// Proceed to change the project. If this throws, the project will be
|
|
// rolled back by the top level handler.
|
|
|
|
(tracks->Any() + &Track::IsSelectedOrSyncLockSelected).Visit(
|
|
#if defined(USE_MIDI)
|
|
[](NoteTrack*) {
|
|
//if NoteTrack, it was cut, so do not clear anything
|
|
|
|
// PRL: But what if it was sync lock selected only, not selected?
|
|
},
|
|
#endif
|
|
[&](WaveTrack *wt, const Track::Fallthrough &fallthrough) {
|
|
if (gPrefs->Read(wxT("/GUI/EnableCutLines"), (long)0)) {
|
|
wt->ClearAndAddCutLine(
|
|
selectedRegion.t0(),
|
|
selectedRegion.t1());
|
|
}
|
|
else
|
|
fallthrough();
|
|
},
|
|
[&](Track *n) {
|
|
n->Clear(selectedRegion.t0(),
|
|
selectedRegion.t1());
|
|
}
|
|
);
|
|
|
|
AudacityProject::msClipT0 = selectedRegion.t0();
|
|
AudacityProject::msClipT1 = selectedRegion.t1();
|
|
AudacityProject::msClipProject = &project;
|
|
|
|
selectedRegion.collapseToT0();
|
|
|
|
project.PushState(_("Cut to the clipboard"), _("Cut"));
|
|
|
|
// Bug 1663
|
|
//mRuler->ClearPlayRegion();
|
|
ruler->DrawOverlays( true );
|
|
|
|
project.RedrawProject();
|
|
|
|
if (historyWindow)
|
|
historyWindow->UpdateDisplay();
|
|
}
|
|
|
|
|
|
void MenuCommandHandler::OnSplitCut(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tracks = project.GetTracks();
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
auto historyWindow = project.GetHistoryWindow();
|
|
|
|
AudacityProject::ClearClipboard();
|
|
|
|
auto pNewClipboard = TrackList::Create();
|
|
auto &newClipboard = *pNewClipboard;
|
|
|
|
Track::Holder dest;
|
|
|
|
tracks->Selected().Visit(
|
|
[&](WaveTrack *n) {
|
|
dest = n->SplitCut(
|
|
selectedRegion.t0(),
|
|
selectedRegion.t1());
|
|
if (dest)
|
|
FinishCopy(n, std::move(dest), newClipboard);
|
|
},
|
|
[&](Track *n) {
|
|
dest = n->Copy(selectedRegion.t0(),
|
|
selectedRegion.t1());
|
|
n->Silence(selectedRegion.t0(),
|
|
selectedRegion.t1());
|
|
if (dest)
|
|
FinishCopy(n, std::move(dest), newClipboard);
|
|
}
|
|
);
|
|
|
|
// Survived possibility of exceptions. Commit changes to the clipboard now.
|
|
newClipboard.Swap(*AudacityProject::msClipboard);
|
|
|
|
AudacityProject::msClipT0 = selectedRegion.t0();
|
|
AudacityProject::msClipT1 = selectedRegion.t1();
|
|
AudacityProject::msClipProject = &project;
|
|
|
|
project.PushState(_("Split-cut to the clipboard"), _("Split Cut"));
|
|
|
|
project.RedrawProject();
|
|
|
|
if (historyWindow)
|
|
historyWindow->UpdateDisplay();
|
|
}
|
|
|
|
|
|
void MenuCommandHandler::OnCopy(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tracks = project.GetTracks();
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
auto historyWindow = project.GetHistoryWindow();
|
|
|
|
for (auto lt : tracks->Selected< LabelTrack >()) {
|
|
if (lt->CopySelectedText()) {
|
|
//trackPanel->Refresh(false);
|
|
return;
|
|
}
|
|
}
|
|
|
|
AudacityProject::ClearClipboard();
|
|
|
|
auto pNewClipboard = TrackList::Create();
|
|
auto &newClipboard = *pNewClipboard;
|
|
|
|
for (auto n : tracks->Selected()) {
|
|
auto dest = n->Copy(selectedRegion.t0(),
|
|
selectedRegion.t1());
|
|
FinishCopy(n, std::move(dest), newClipboard);
|
|
}
|
|
|
|
// Survived possibility of exceptions. Commit changes to the clipboard now.
|
|
newClipboard.Swap(*AudacityProject::msClipboard);
|
|
|
|
AudacityProject::msClipT0 = selectedRegion.t0();
|
|
AudacityProject::msClipT1 = selectedRegion.t1();
|
|
AudacityProject::msClipProject = &project;
|
|
|
|
//Make sure the menus/toolbar states get updated
|
|
trackPanel->Refresh(false);
|
|
|
|
if (historyWindow)
|
|
historyWindow->UpdateDisplay();
|
|
}
|
|
|
|
void MenuCommandHandler::OnPaste(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tracks = project.GetTracks();
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto trackFactory = project.GetTrackFactory();
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
auto isSyncLocked = project.IsSyncLocked();
|
|
|
|
// Handle text paste (into active label) first.
|
|
if (this->HandlePasteText(project))
|
|
return;
|
|
|
|
// If nothing's selected, we just insert NEW tracks.
|
|
if (this->HandlePasteNothingSelected(project))
|
|
return;
|
|
|
|
auto clipTrackRange = AudacityProject::msClipboard->Any< const Track >();
|
|
if (clipTrackRange.empty())
|
|
return;
|
|
|
|
// Otherwise, paste into the selected tracks.
|
|
double t0 = selectedRegion.t0();
|
|
double t1 = selectedRegion.t1();
|
|
|
|
auto pN = tracks->Any().begin();
|
|
|
|
Track *ff = NULL;
|
|
const Track *lastClipBeforeMismatch = NULL;
|
|
const Track *mismatchedClip = NULL;
|
|
const Track *prevClip = NULL;
|
|
|
|
bool bAdvanceClipboard = true;
|
|
bool bPastedSomething = false;
|
|
|
|
auto pC = clipTrackRange.begin();
|
|
size_t nnChannels=0, ncChannels=0;
|
|
while (*pN && *pC) {
|
|
auto n = *pN;
|
|
auto c = *pC;
|
|
if (n->GetSelected()) {
|
|
bAdvanceClipboard = true;
|
|
if (mismatchedClip)
|
|
c = mismatchedClip;
|
|
if (!c->SameKindAs(*n)) {
|
|
if (!mismatchedClip) {
|
|
lastClipBeforeMismatch = prevClip;
|
|
mismatchedClip = c;
|
|
}
|
|
bAdvanceClipboard = false;
|
|
c = lastClipBeforeMismatch;
|
|
|
|
|
|
// If the types still don't match...
|
|
while (c && !c->SameKindAs(*n)) {
|
|
prevClip = c;
|
|
c = * ++ pC;
|
|
}
|
|
}
|
|
|
|
// Handle case where the first track in clipboard
|
|
// is of different type than the first selected track
|
|
if (!c) {
|
|
c = mismatchedClip;
|
|
while (n && (!c->SameKindAs(*n) || !n->GetSelected()))
|
|
{
|
|
// Must perform sync-lock adjustment before incrementing n
|
|
if (n->IsSyncLockSelected()) {
|
|
auto newT1 = t0 +
|
|
(AudacityProject::msClipT1 - AudacityProject::msClipT0);
|
|
if (t1 != newT1 && t1 <= n->GetEndTime()) {
|
|
n->SyncLockAdjust(t1, newT1);
|
|
bPastedSomething = true;
|
|
}
|
|
}
|
|
n = * ++ pN;
|
|
}
|
|
if (!n)
|
|
c = NULL;
|
|
}
|
|
|
|
// The last possible case for cross-type pastes: triggered when we try to
|
|
// paste 1+ tracks from one type into 1+ tracks of another type. If
|
|
// there's a mix of types, this shouldn't run.
|
|
if (!c)
|
|
// Throw, so that any previous changes to the project in this loop
|
|
// are discarded.
|
|
throw SimpleMessageBoxException{
|
|
_("Pasting one type of track into another is not allowed.")
|
|
};
|
|
|
|
// We should need this check only each time we visit the leading
|
|
// channel
|
|
if ( n->IsLeader() ) {
|
|
wxASSERT( c->IsLeader() ); // the iteration logic should ensure this
|
|
|
|
auto cChannels = TrackList::Channels(c);
|
|
ncChannels = cChannels.size();
|
|
auto nChannels = TrackList::Channels(n);
|
|
nnChannels = nChannels.size();
|
|
|
|
// When trying to copy from stereo to mono track, show error and exit
|
|
// TODO: Automatically offer user to mix down to mono (unfortunately
|
|
// this is not easy to implement
|
|
if (ncChannels > nnChannels)
|
|
{
|
|
if (ncChannels > 2) {
|
|
// TODO: more-than-two-channels-message
|
|
// Re-word the error message
|
|
}
|
|
// else
|
|
|
|
// Throw, so that any previous changes to the project in this loop
|
|
// are discarded.
|
|
throw SimpleMessageBoxException{
|
|
_("Copying stereo audio into a mono track is not allowed.")
|
|
};
|
|
}
|
|
}
|
|
|
|
if (!ff)
|
|
ff = n;
|
|
|
|
wxASSERT( n && c && n->SameKindAs(*c) );
|
|
Maybe<WaveTrack::Locker> locker;
|
|
|
|
n->TypeSwitch(
|
|
[&](WaveTrack *wn){
|
|
const auto wc = static_cast<const WaveTrack *>(c);
|
|
if (AudacityProject::msClipProject != &project)
|
|
// Cause duplication of block files on disk, when copy is
|
|
// between projects
|
|
locker.create(wc);
|
|
bPastedSomething = true;
|
|
wn->ClearAndPaste(t0, t1, wc, true, true);
|
|
},
|
|
[&](LabelTrack *ln){
|
|
// Per Bug 293, users expect labels to move on a paste into
|
|
// a label track.
|
|
ln->Clear(t0, t1);
|
|
|
|
ln->ShiftLabelsOnInsert(
|
|
AudacityProject::msClipT1 - AudacityProject::msClipT0, t0);
|
|
|
|
bPastedSomething |= ln->PasteOver(t0, c);
|
|
},
|
|
[&](Track *){
|
|
bPastedSomething = true;
|
|
n->Clear(t0, t1);
|
|
n->Paste(t0, c);
|
|
}
|
|
);
|
|
|
|
--nnChannels;
|
|
--ncChannels;
|
|
|
|
// When copying from mono to stereo track, paste the wave form
|
|
// to both channels
|
|
// TODO: more-than-two-channels
|
|
// This will replicate the last pasted channel as many times as needed
|
|
while (nnChannels > 0 && ncChannels == 0)
|
|
{
|
|
n = * ++ pN;
|
|
--nnChannels;
|
|
|
|
n->TypeSwitch(
|
|
[&](WaveTrack *wn){
|
|
bPastedSomething = true;
|
|
// Note: rely on locker being still be in scope!
|
|
wn->ClearAndPaste(t0, t1, c, true, true);
|
|
},
|
|
[&](Track *){
|
|
n->Clear(t0, t1);
|
|
bPastedSomething = true;
|
|
n->Paste(t0, c);
|
|
}
|
|
);
|
|
}
|
|
|
|
if (bAdvanceClipboard) {
|
|
prevClip = c;
|
|
c = * ++ pC;
|
|
}
|
|
} // if (n->GetSelected())
|
|
else if (n->IsSyncLockSelected())
|
|
{
|
|
auto newT1 = t0 +
|
|
(AudacityProject::msClipT1 - AudacityProject::msClipT0);
|
|
if (t1 != newT1 && t1 <= n->GetEndTime()) {
|
|
n->SyncLockAdjust(t1, newT1);
|
|
bPastedSomething = true;
|
|
}
|
|
}
|
|
++pN;
|
|
}
|
|
|
|
// This block handles the cases where our clipboard is smaller
|
|
// than the amount of selected destination tracks. We take the
|
|
// last wave track, and paste that one into the remaining
|
|
// selected tracks.
|
|
if ( *pN && ! *pC )
|
|
{
|
|
const auto wc =
|
|
*AudacityProject::msClipboard->Any< const WaveTrack >().rbegin();
|
|
Maybe<WaveTrack::Locker> locker;
|
|
if (AudacityProject::msClipProject != &project && wc)
|
|
// Cause duplication of block files on disk, when copy is
|
|
// between projects
|
|
locker.create(static_cast<const WaveTrack*>(wc));
|
|
|
|
tracks->Any().StartingWith(*pN).Visit(
|
|
[&](WaveTrack *wt, const Track::Fallthrough &fallthrough) {
|
|
if (!wt->GetSelected())
|
|
return fallthrough();
|
|
|
|
if (wc) {
|
|
bPastedSomething = true;
|
|
wt->ClearAndPaste(t0, t1, wc, true, true);
|
|
}
|
|
else {
|
|
auto tmp = trackFactory->NewWaveTrack(
|
|
wt->GetSampleFormat(), wt->GetRate());
|
|
tmp->InsertSilence(0.0,
|
|
AudacityProject::msClipT1 - AudacityProject::msClipT0); // MJS: Is this correct?
|
|
tmp->Flush();
|
|
|
|
bPastedSomething = true;
|
|
wt->ClearAndPaste(t0, t1, tmp.get(), true, true);
|
|
}
|
|
},
|
|
[&](LabelTrack *lt, const Track::Fallthrough &fallthrough) {
|
|
if (!lt->GetSelected())
|
|
return fallthrough();
|
|
|
|
lt->Clear(t0, t1);
|
|
|
|
// As above, only shift labels if sync-lock is on.
|
|
if (isSyncLocked)
|
|
lt->ShiftLabelsOnInsert(
|
|
AudacityProject::msClipT1 - AudacityProject::msClipT0, t0);
|
|
},
|
|
[&](Track *n) {
|
|
if (n->IsSyncLockSelected())
|
|
n->SyncLockAdjust(t1, t0 +
|
|
AudacityProject::msClipT1 - AudacityProject::msClipT0);
|
|
}
|
|
);
|
|
}
|
|
|
|
// TODO: What if we clicked past the end of the track?
|
|
|
|
if (bPastedSomething)
|
|
{
|
|
selectedRegion.setT1(
|
|
t0 + AudacityProject::msClipT1 - AudacityProject::msClipT0);
|
|
|
|
project.PushState(_("Pasted from the clipboard"), _("Paste"));
|
|
|
|
project.RedrawProject();
|
|
|
|
if (ff)
|
|
trackPanel->EnsureVisible(ff);
|
|
}
|
|
}
|
|
|
|
// Handle text paste (into active label), if any. Return true if did paste.
|
|
// (This was formerly the first part of overly-long OnPaste.)
|
|
bool MenuCommandHandler::HandlePasteText(AudacityProject &project)
|
|
{
|
|
auto tracks = project.GetTracks();
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
|
|
for (auto pLabelTrack : tracks->Any<LabelTrack>())
|
|
{
|
|
// Does this track have an active label?
|
|
if (pLabelTrack->IsSelected()) {
|
|
|
|
// Yes, so try pasting into it
|
|
if (pLabelTrack->PasteSelectedText(selectedRegion.t0(),
|
|
selectedRegion.t1()))
|
|
{
|
|
project.PushState(_("Pasted text from the clipboard"), _("Paste"));
|
|
|
|
// Make sure caret is in view
|
|
int x;
|
|
if (pLabelTrack->CalcCursorX(&x)) {
|
|
trackPanel->ScrollIntoView(x);
|
|
}
|
|
|
|
// Redraw everyting (is that necessary???) and bail
|
|
project.RedrawProject();
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Return true if nothing selected, regardless of paste result.
|
|
// If nothing was selected, create and paste into NEW tracks.
|
|
// (This was formerly the second part of overly-long OnPaste.)
|
|
bool MenuCommandHandler::HandlePasteNothingSelected(AudacityProject &project)
|
|
{
|
|
auto tracks = project.GetTracks();
|
|
auto trackFactory = project.GetTrackFactory();
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
|
|
// First check whether anything's selected.
|
|
if (tracks->Selected())
|
|
return false;
|
|
else
|
|
{
|
|
auto clipTrackRange = AudacityProject::msClipboard->Any< const Track >();
|
|
if (clipTrackRange.empty())
|
|
return true; // nothing to paste
|
|
|
|
Track* pFirstNewTrack = NULL;
|
|
for (auto pClip : clipTrackRange) {
|
|
Maybe<WaveTrack::Locker> locker;
|
|
|
|
Track::Holder uNewTrack;
|
|
Track *pNewTrack;
|
|
pClip->TypeSwitch(
|
|
[&](const WaveTrack *wc) {
|
|
if ((AudacityProject::msClipProject != &project))
|
|
// Cause duplication of block files on disk, when copy is
|
|
// between projects
|
|
locker.create(wc);
|
|
uNewTrack = trackFactory->NewWaveTrack(
|
|
wc->GetSampleFormat(), wc->GetRate()),
|
|
pNewTrack = uNewTrack.get();
|
|
},
|
|
#ifdef USE_MIDI
|
|
[&](const NoteTrack *) {
|
|
uNewTrack = trackFactory->NewNoteTrack(),
|
|
pNewTrack = uNewTrack.get();
|
|
},
|
|
#endif
|
|
[&](const LabelTrack *) {
|
|
uNewTrack = trackFactory->NewLabelTrack(),
|
|
pNewTrack = uNewTrack.get();
|
|
},
|
|
[&](const TimeTrack *) {
|
|
// Maintain uniqueness of the time track!
|
|
pNewTrack = tracks->GetTimeTrack();
|
|
if (!pNewTrack)
|
|
uNewTrack = trackFactory->NewTimeTrack(),
|
|
pNewTrack = uNewTrack.get();
|
|
}
|
|
);
|
|
|
|
wxASSERT(pClip);
|
|
|
|
pNewTrack->Paste(0.0, pClip);
|
|
|
|
if (!pFirstNewTrack)
|
|
pFirstNewTrack = pNewTrack;
|
|
|
|
pNewTrack->SetSelected(true);
|
|
if (uNewTrack)
|
|
FinishCopy(pClip, std::move(uNewTrack), *tracks);
|
|
else
|
|
Track::FinishCopy(pClip, pNewTrack);
|
|
}
|
|
|
|
// Select some pasted samples, which is probably impossible to get right
|
|
// with various project and track sample rates.
|
|
// So do it at the sample rate of the project
|
|
AudacityProject *p = GetActiveProject();
|
|
double projRate = p->GetRate();
|
|
double quantT0 = QUANTIZED_TIME(AudacityProject::msClipT0, projRate);
|
|
double quantT1 = QUANTIZED_TIME(AudacityProject::msClipT1, projRate);
|
|
selectedRegion.setTimes(
|
|
0.0, // anywhere else and this should be
|
|
// half a sample earlier
|
|
quantT1 - quantT0);
|
|
|
|
project.PushState(_("Pasted from the clipboard"), _("Paste"));
|
|
|
|
project.RedrawProject();
|
|
|
|
if (pFirstNewTrack)
|
|
trackPanel->EnsureVisible(pFirstNewTrack);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
// Creates a NEW label in each selected label track with text from the system
|
|
// clipboard
|
|
void MenuCommandHandler::OnPasteNewLabel(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tracks = project.GetTracks();
|
|
auto trackFactory = project.GetTrackFactory();
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
|
|
bool bPastedSomething = false;
|
|
|
|
{
|
|
auto trackRange = tracks->Selected< const LabelTrack >();
|
|
if (trackRange.empty())
|
|
{
|
|
// If there are no selected label tracks, try to choose the first label
|
|
// track after some other selected track
|
|
Track *t = *tracks->Selected().begin()
|
|
.Filter( &Track::Any )
|
|
.Filter<LabelTrack>();
|
|
|
|
// If no match found, add one
|
|
if (!t) {
|
|
t = tracks->Add(trackFactory->NewLabelTrack());
|
|
}
|
|
|
|
// Select this track so the loop picks it up
|
|
t->SetSelected(true);
|
|
}
|
|
}
|
|
|
|
LabelTrack *plt = NULL; // the previous track
|
|
for ( auto lt : tracks->Selected< LabelTrack >() )
|
|
{
|
|
// Unselect the last label, so we'll have just one active label when
|
|
// we're done
|
|
if (plt)
|
|
plt->Unselect();
|
|
|
|
// Add a NEW label, paste into it
|
|
// Paul L: copy whatever defines the selected region, not just times
|
|
lt->AddLabel(selectedRegion);
|
|
if (lt->PasteSelectedText(selectedRegion.t0(),
|
|
selectedRegion.t1()))
|
|
bPastedSomething = true;
|
|
|
|
// Set previous track
|
|
plt = lt;
|
|
}
|
|
|
|
// plt should point to the last label track pasted to -- ensure it's visible
|
|
// and set focus
|
|
if (plt) {
|
|
trackPanel->EnsureVisible(plt);
|
|
trackPanel->SetFocus();
|
|
}
|
|
|
|
if (bPastedSomething) {
|
|
project.PushState(_("Pasted from the clipboard"), _("Paste Text to New Label"));
|
|
|
|
// Is this necessary? (carried over from former logic in OnPaste())
|
|
project.RedrawProject();
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::OnPasteOver(const CommandContext &context) // not currently in use it appears
|
|
{
|
|
auto &project = context.project;
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
|
|
if((AudacityProject::msClipT1 - AudacityProject::msClipT0) > 0.0)
|
|
{
|
|
selectedRegion.setT1(
|
|
selectedRegion.t0() +
|
|
(AudacityProject::msClipT1 - AudacityProject::msClipT0));
|
|
// MJS: pointless, given what we do in OnPaste?
|
|
}
|
|
OnPaste(context);
|
|
|
|
return;
|
|
}
|
|
|
|
void MenuCommandHandler::OnTrim(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tracks = project.GetTracks();
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
|
|
if (selectedRegion.isPoint())
|
|
return;
|
|
|
|
tracks->Selected().Visit(
|
|
#ifdef USE_MIDI
|
|
[&](NoteTrack *nt) {
|
|
nt->Trim(selectedRegion.t0(),
|
|
selectedRegion.t1());
|
|
},
|
|
#endif
|
|
[&](WaveTrack *wt) {
|
|
//Delete the section before the left selector
|
|
wt->Trim(selectedRegion.t0(),
|
|
selectedRegion.t1());
|
|
}
|
|
);
|
|
|
|
project.PushState(
|
|
wxString::Format(
|
|
_("Trim selected audio tracks from %.2f seconds to %.2f seconds"),
|
|
selectedRegion.t0(), selectedRegion.t1()),
|
|
_("Trim Audio"));
|
|
|
|
project.RedrawProject();
|
|
}
|
|
|
|
void MenuCommandHandler::OnDelete(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
project.Clear();
|
|
}
|
|
|
|
void MenuCommandHandler::OnSplitDelete(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tracks = project.GetTracks();
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
|
|
tracks->Selected().Visit(
|
|
[&](WaveTrack *wt) {
|
|
wt->SplitDelete(selectedRegion.t0(),
|
|
selectedRegion.t1());
|
|
},
|
|
[&](Track *n) {
|
|
n->Silence(selectedRegion.t0(),
|
|
selectedRegion.t1());
|
|
}
|
|
);
|
|
|
|
project.PushState(
|
|
wxString::Format(_("Split-deleted %.2f seconds at t=%.2f"),
|
|
selectedRegion.duration(),
|
|
selectedRegion.t0()),
|
|
_("Split Delete"));
|
|
|
|
project.RedrawProject();
|
|
}
|
|
|
|
void MenuCommandHandler::OnDisjoin(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tracks = project.GetTracks();
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
|
|
for (auto wt : tracks->Selected< WaveTrack >())
|
|
wt->Disjoin(selectedRegion.t0(),
|
|
selectedRegion.t1());
|
|
|
|
project.PushState(
|
|
wxString::Format(_("Detached %.2f seconds at t=%.2f"),
|
|
selectedRegion.duration(),
|
|
selectedRegion.t0()),
|
|
_("Detach"));
|
|
|
|
project.RedrawProject();
|
|
}
|
|
|
|
void MenuCommandHandler::OnJoin(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tracks = project.GetTracks();
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
|
|
for (auto wt : tracks->Selected< WaveTrack >())
|
|
wt->Join(selectedRegion.t0(),
|
|
selectedRegion.t1());
|
|
|
|
project.PushState(
|
|
wxString::Format(_("Joined %.2f seconds at t=%.2f"),
|
|
selectedRegion.duration(),
|
|
selectedRegion.t0()),
|
|
_("Join"));
|
|
|
|
project.RedrawProject();
|
|
}
|
|
|
|
void MenuCommandHandler::OnSilence(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tracks = project.GetTracks();
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
|
|
for ( auto n : tracks->Selected< AudioTrack >() )
|
|
n->Silence(selectedRegion.t0(), selectedRegion.t1());
|
|
|
|
project.PushState(
|
|
wxString::Format(_("Silenced selected tracks for %.2f seconds at %.2f"),
|
|
selectedRegion.duration(),
|
|
selectedRegion.t0()),
|
|
_("Silence"));
|
|
|
|
trackPanel->Refresh(false);
|
|
}
|
|
|
|
void MenuCommandHandler::OnDuplicate(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tracks = project.GetTracks();
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
|
|
// This iteration is unusual because we add to the list inside the loop
|
|
auto range = tracks->Selected();
|
|
auto last = *range.rbegin();
|
|
for (auto n : range) {
|
|
// Make copies not for clipboard but for direct addition to the project
|
|
auto dest = n->Copy(selectedRegion.t0(),
|
|
selectedRegion.t1(), false);
|
|
dest->Init(*n);
|
|
dest->SetOffset(wxMax(selectedRegion.t0(), n->GetOffset()));
|
|
tracks->Add(std::move(dest));
|
|
|
|
// This break is really needed, else we loop infinitely
|
|
if (n == last)
|
|
break;
|
|
}
|
|
|
|
project.PushState(_("Duplicated"), _("Duplicate"));
|
|
|
|
project.RedrawProject();
|
|
}
|
|
|
|
void MenuCommandHandler::OnCutLabels(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
|
|
if( selectedRegion.isPoint() )
|
|
return;
|
|
|
|
// Because of grouping the copy may need to operate on different tracks than
|
|
// the clear, so we do these actions separately.
|
|
project.EditClipboardByLabel( &WaveTrack::CopyNonconst );
|
|
|
|
if( gPrefs->Read( wxT( "/GUI/EnableCutLines" ), ( long )0 ) )
|
|
project.EditByLabel( &WaveTrack::ClearAndAddCutLine, true );
|
|
else
|
|
project.EditByLabel( &WaveTrack::Clear, true );
|
|
|
|
AudacityProject::msClipProject = &project;
|
|
|
|
selectedRegion.collapseToT0();
|
|
|
|
project.PushState(
|
|
/* i18n-hint: (verb) past tense. Audacity has just cut the labeled audio regions.*/
|
|
_( "Cut labeled audio regions to clipboard" ),
|
|
/* i18n-hint: (verb)*/
|
|
_( "Cut Labeled Audio" ) );
|
|
|
|
project.RedrawProject();
|
|
}
|
|
|
|
void MenuCommandHandler::OnSplitCutLabels(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
|
|
if( selectedRegion.isPoint() )
|
|
return;
|
|
|
|
project.EditClipboardByLabel( &WaveTrack::SplitCut );
|
|
|
|
AudacityProject::msClipProject = &project;
|
|
|
|
project.PushState(
|
|
/* i18n-hint: (verb) Audacity has just split cut the labeled audio regions*/
|
|
_( "Split Cut labeled audio regions to clipboard" ),
|
|
/* i18n-hint: (verb) Do a special kind of cut on the labels*/
|
|
_( "Split Cut Labeled Audio" ) );
|
|
|
|
project.RedrawProject();
|
|
}
|
|
|
|
void MenuCommandHandler::OnCopyLabels(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
|
|
if( selectedRegion.isPoint() )
|
|
return;
|
|
|
|
project.EditClipboardByLabel( &WaveTrack::CopyNonconst );
|
|
|
|
AudacityProject::msClipProject = &project;
|
|
|
|
project.PushState( _( "Copied labeled audio regions to clipboard" ),
|
|
/* i18n-hint: (verb)*/
|
|
_( "Copy Labeled Audio" ) );
|
|
|
|
trackPanel->Refresh( false );
|
|
}
|
|
|
|
void MenuCommandHandler::OnDeleteLabels(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
|
|
if( selectedRegion.isPoint() )
|
|
return;
|
|
|
|
project.EditByLabel( &WaveTrack::Clear, true );
|
|
|
|
selectedRegion.collapseToT0();
|
|
|
|
project.PushState(
|
|
/* i18n-hint: (verb) Audacity has just deleted the labeled audio regions*/
|
|
_( "Deleted labeled audio regions" ),
|
|
/* i18n-hint: (verb)*/
|
|
_( "Delete Labeled Audio" ) );
|
|
|
|
project.RedrawProject();
|
|
}
|
|
|
|
void MenuCommandHandler::OnSplitDeleteLabels(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
|
|
if( selectedRegion.isPoint() )
|
|
return;
|
|
|
|
project.EditByLabel( &WaveTrack::SplitDelete, false );
|
|
|
|
project.PushState(
|
|
/* i18n-hint: (verb) Audacity has just done a special kind of DELETE on the labeled audio regions */
|
|
_( "Split Deleted labeled audio regions" ),
|
|
/* i18n-hint: (verb) Do a special kind of DELETE on labeled audio regions*/
|
|
_( "Split Delete Labeled Audio" ) );
|
|
|
|
project.RedrawProject();
|
|
}
|
|
|
|
void MenuCommandHandler::OnSilenceLabels(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
|
|
if( selectedRegion.isPoint() )
|
|
return;
|
|
|
|
project.EditByLabel( &WaveTrack::Silence, false );
|
|
|
|
project.PushState(
|
|
/* i18n-hint: (verb)*/
|
|
_( "Silenced labeled audio regions" ),
|
|
/* i18n-hint: (verb)*/
|
|
_( "Silence Labeled Audio" ) );
|
|
|
|
trackPanel->Refresh( false );
|
|
}
|
|
|
|
void MenuCommandHandler::OnSplitLabels(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
|
|
project.EditByLabel( &WaveTrack::Split, false );
|
|
|
|
project.PushState(
|
|
/* i18n-hint: (verb) past tense. Audacity has just split the labeled audio (a point or a region)*/
|
|
_( "Split labeled audio (points or regions)" ),
|
|
/* i18n-hint: (verb)*/
|
|
_( "Split Labeled Audio" ) );
|
|
|
|
project.RedrawProject();
|
|
}
|
|
|
|
void MenuCommandHandler::OnJoinLabels(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
|
|
if( selectedRegion.isPoint() )
|
|
return;
|
|
|
|
project.EditByLabel( &WaveTrack::Join, false );
|
|
|
|
project.PushState(
|
|
/* i18n-hint: (verb) Audacity has just joined the labeled audio (points or regions)*/
|
|
_( "Joined labeled audio (points or regions)" ),
|
|
/* i18n-hint: (verb)*/
|
|
_( "Join Labeled Audio" ) );
|
|
|
|
project.RedrawProject();
|
|
}
|
|
|
|
void MenuCommandHandler::OnDisjoinLabels(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
|
|
if( selectedRegion.isPoint() )
|
|
return;
|
|
|
|
project.EditByLabel( &WaveTrack::Disjoin, false );
|
|
|
|
project.PushState(
|
|
/* i18n-hint: (verb) Audacity has just detached the labeled audio regions.
|
|
This message appears in history and tells you about something
|
|
Audacity has done.*/
|
|
_( "Detached labeled audio regions" ),
|
|
/* i18n-hint: (verb)*/
|
|
_( "Detach Labeled Audio" ) );
|
|
|
|
project.RedrawProject();
|
|
}
|
|
|
|
void MenuCommandHandler::OnSplit(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tracks = project.GetTracks();
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
|
|
double sel0 = selectedRegion.t0();
|
|
double sel1 = selectedRegion.t1();
|
|
|
|
for (auto wt : tracks->Selected< WaveTrack >())
|
|
wt->Split( sel0, sel1 );
|
|
|
|
project.PushState(_("Split"), _("Split"));
|
|
trackPanel->Refresh(false);
|
|
#if 0
|
|
//ANSWER-ME: Do we need to keep this commented out OnSplit() code?
|
|
// This whole section no longer used...
|
|
/*
|
|
* Previous (pre-multiclip) implementation of "Split" command
|
|
* This does work only when a range is selected!
|
|
*
|
|
TrackListIterator iter(tracks);
|
|
|
|
Track *n = iter.First();
|
|
Track *dest;
|
|
|
|
TrackList newTracks;
|
|
|
|
while (n) {
|
|
if (n->GetSelected()) {
|
|
double sel0 = selectedRegion.t0();
|
|
double sel1 = selectedRegion.t1();
|
|
|
|
dest = n->Copy(sel0, sel1);
|
|
dest->Init(*n);
|
|
dest->SetOffset(wxMax(sel0, n->GetOffset()));
|
|
|
|
if (sel1 >= n->GetEndTime())
|
|
n->Clear(sel0, sel1);
|
|
else if (sel0 <= n->GetOffset()) {
|
|
n->Clear(sel0, sel1);
|
|
n->SetOffset(sel1);
|
|
} else
|
|
n->Silence(sel0, sel1);
|
|
|
|
newTracks.Add(dest);
|
|
}
|
|
n = iter.Next();
|
|
}
|
|
|
|
TrackListIterator nIter(&newTracks);
|
|
n = nIter.First();
|
|
while (n) {
|
|
tracks->Add(n);
|
|
n = nIter.Next();
|
|
}
|
|
|
|
PushState(_("Split"), _("Split"));
|
|
|
|
FixScrollbars();
|
|
trackPanel->Refresh(false);
|
|
*/
|
|
#endif
|
|
}
|
|
|
|
void MenuCommandHandler::OnSplitNew(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tracks = project.GetTracks();
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
|
|
Track::Holder dest;
|
|
|
|
// This iteration is unusual because we add to the list inside the loop
|
|
auto range = tracks->Selected();
|
|
auto last = *range.rbegin();
|
|
for (auto track : range) {
|
|
track->TypeSwitch(
|
|
[&](WaveTrack *wt) {
|
|
// Clips must be aligned to sample positions or the NEW clip will not fit in the gap where it came from
|
|
double offset = wt->GetOffset();
|
|
offset = wt->LongSamplesToTime(wt->TimeToLongSamples(offset));
|
|
double newt0 = wt->LongSamplesToTime(wt->TimeToLongSamples(
|
|
selectedRegion.t0()));
|
|
double newt1 = wt->LongSamplesToTime(wt->TimeToLongSamples(
|
|
selectedRegion.t1()));
|
|
dest = wt->SplitCut(newt0, newt1);
|
|
if (dest) {
|
|
dest->SetOffset(wxMax(newt0, offset));
|
|
FinishCopy(wt, std::move(dest), *tracks);
|
|
}
|
|
}
|
|
#if 0
|
|
,
|
|
// LL: For now, just skip all non-wave tracks since the other do not
|
|
// yet support proper splitting.
|
|
[&](Track *n) {
|
|
dest = n->Cut(viewInfo.selectedRegion.t0(),
|
|
viewInfo.selectedRegion.t1());
|
|
if (dest) {
|
|
dest->SetOffset(wxMax(0, n->GetOffset()));
|
|
FinishCopy(n, std::move(dest), *tracks);
|
|
}
|
|
}
|
|
#endif
|
|
);
|
|
if (track == last)
|
|
break;
|
|
}
|
|
|
|
project.PushState(_("Split to new track"), _("Split New"));
|
|
|
|
project.RedrawProject();
|
|
}
|
|
|
|
int MenuCommandHandler::CountSelectedTracks(TrackList &tracks)
|
|
{
|
|
return tracks.Selected().size();
|
|
}
|
|
|
|
void MenuCommandHandler::OnSelectTimeAndTracks
|
|
(AudacityProject &project, bool bAllTime, bool bAllTracks)
|
|
{
|
|
auto tracks = project.GetTracks();
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
auto mixerBoard = project.GetMixerBoard();
|
|
|
|
if( bAllTime )
|
|
selectedRegion.setTimes(
|
|
tracks->GetMinOffset(), tracks->GetEndTime());
|
|
|
|
if( bAllTracks ) {
|
|
for (auto t : tracks->Any())
|
|
t->SetSelected(true);
|
|
|
|
project.ModifyState(false);
|
|
trackPanel->Refresh(false);
|
|
if (mixerBoard)
|
|
mixerBoard->Refresh(false);
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::OnSelectAllTime(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
OnSelectTimeAndTracks( project, true, false );
|
|
}
|
|
|
|
void MenuCommandHandler::OnSelectAllTracks(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
OnSelectTimeAndTracks( project, false, true );
|
|
}
|
|
|
|
void MenuCommandHandler::OnSelectAll(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
OnSelectTimeAndTracks( project, true, true );
|
|
}
|
|
|
|
// This function selects all tracks if no tracks selected,
|
|
// and all time if no time selected.
|
|
// There is an argument for making it just count wave tracks,
|
|
// However you could then not select a label and cut it,
|
|
// without this function selecting all tracks.
|
|
void MenuCommandHandler::OnSelectSomething(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tracks = project.GetTracks();
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
|
|
bool bTime = selectedRegion.isPoint();
|
|
bool bTracks = CountSelectedTracks(*tracks) == 0;
|
|
|
|
if( bTime || bTracks )
|
|
OnSelectTimeAndTracks( project, bTime, bTracks );
|
|
}
|
|
|
|
void AudacityProject::SelectNone()
|
|
{
|
|
for (auto t : GetTracks()->Any())
|
|
t->SetSelected(false);
|
|
|
|
mTrackPanel->Refresh(false);
|
|
if (mMixerBoard)
|
|
mMixerBoard->Refresh(false);
|
|
}
|
|
|
|
void MenuCommandHandler::OnSelectNone(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
|
|
selectedRegion.collapseToT0();
|
|
project.SelectNone();
|
|
project.ModifyState(false);
|
|
}
|
|
|
|
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
|
|
void MenuCommandHandler::OnToggleSpectralSelection(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto &selectedRegion = project.GetSelection();
|
|
|
|
const double f0 = selectedRegion.f0();
|
|
const double f1 = selectedRegion.f1();
|
|
const bool haveSpectralSelection =
|
|
!(f0 == SelectedRegion::UndefinedFrequency &&
|
|
f1 == SelectedRegion::UndefinedFrequency);
|
|
if (haveSpectralSelection)
|
|
{
|
|
mLastF0 = f0;
|
|
mLastF1 = f1;
|
|
selectedRegion.setFrequencies
|
|
(SelectedRegion::UndefinedFrequency, SelectedRegion::UndefinedFrequency);
|
|
}
|
|
else
|
|
selectedRegion.setFrequencies(mLastF0, mLastF1);
|
|
|
|
trackPanel->Refresh(false);
|
|
project.ModifyState(false);
|
|
}
|
|
|
|
void MenuCommandHandler::DoNextPeakFrequency(AudacityProject &project, bool up)
|
|
{
|
|
auto tracks = project.GetTracks();
|
|
auto &viewInfo = project.GetViewInfo();
|
|
auto trackPanel = project.GetTrackPanel();
|
|
|
|
// Find the first selected wave track that is in a spectrogram view.
|
|
const WaveTrack *pTrack {};
|
|
for ( auto wt : tracks->Selected< const WaveTrack >() ) {
|
|
const int display = wt->GetDisplay();
|
|
if (display == WaveTrack::Spectrum) {
|
|
pTrack = wt;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (pTrack) {
|
|
SpectrumAnalyst analyst;
|
|
SelectHandle::SnapCenterOnce(analyst, viewInfo, pTrack, up);
|
|
trackPanel->Refresh(false);
|
|
project.ModifyState(false);
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::OnNextHigherPeakFrequency(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
DoNextPeakFrequency(project, true);
|
|
}
|
|
|
|
|
|
void MenuCommandHandler::OnNextLowerPeakFrequency(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
DoNextPeakFrequency(project, false);
|
|
}
|
|
#endif
|
|
|
|
void MenuCommandHandler::OnSelectCursorEnd(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tracks = project.GetTracks();
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
|
|
double kWayOverToLeft = std::numeric_limits<double>::lowest();
|
|
|
|
auto range = tracks->Selected();
|
|
if ( ! range )
|
|
return;
|
|
|
|
double maxEndOffset = range.max( &Track::GetEndTime );
|
|
|
|
if( maxEndOffset <=
|
|
(kWayOverToLeft * (1 - std::numeric_limits<double>::epsilon()) ))
|
|
return;
|
|
|
|
selectedRegion.setT1(maxEndOffset);
|
|
|
|
project.ModifyState(false);
|
|
|
|
trackPanel->Refresh(false);
|
|
}
|
|
|
|
void MenuCommandHandler::OnSelectStartCursor(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tracks = project.GetTracks();
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
|
|
double kWayOverToRight = std::numeric_limits<double>::max();
|
|
|
|
auto range = tracks->Selected();
|
|
if ( ! range )
|
|
return;
|
|
|
|
double minOffset = range.min( &Track::GetStartTime );
|
|
|
|
if( minOffset >=
|
|
(kWayOverToRight * (1 - std::numeric_limits<double>::epsilon()) ))
|
|
return;
|
|
|
|
selectedRegion.setT0(minOffset);
|
|
|
|
project.ModifyState(false);
|
|
|
|
trackPanel->Refresh(false);
|
|
}
|
|
|
|
void MenuCommandHandler::OnSelectTrackStartToEnd(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto &viewInfo = project.GetViewInfo();
|
|
auto tracks = project.GetTracks();
|
|
auto trackPanel = project.GetTrackPanel();
|
|
|
|
auto range = tracks->Selected();
|
|
double maxEndOffset = range.max( &Track::GetEndTime );
|
|
double minOffset = range.min( &Track::GetStartTime );
|
|
|
|
if( maxEndOffset < minOffset)
|
|
return;
|
|
|
|
viewInfo.selectedRegion.setTimes( minOffset, maxEndOffset );
|
|
project.ModifyState(false);
|
|
|
|
trackPanel->Refresh(false);
|
|
}
|
|
|
|
|
|
void MenuCommandHandler::OnSelectPrevClipBoundaryToCursor
|
|
(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
DoSelectClipBoundary(project, false);
|
|
}
|
|
|
|
void MenuCommandHandler::OnSelectCursorToNextClipBoundary
|
|
(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
DoSelectClipBoundary(project, true);
|
|
}
|
|
|
|
void MenuCommandHandler::DoSelectClipBoundary(AudacityProject &project, bool next)
|
|
{
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
auto trackPanel = project.GetTrackPanel();
|
|
|
|
std::vector<FoundClipBoundary> results;
|
|
FindClipBoundaries(project, next ? selectedRegion.t1() :
|
|
selectedRegion.t0(), next, results);
|
|
|
|
if (results.size() > 0) {
|
|
// note that if there is more than one result, each has the same time value.
|
|
if (next)
|
|
selectedRegion.setT1(results[0].time);
|
|
else
|
|
selectedRegion.setT0(results[0].time);
|
|
|
|
project.ModifyState(false);
|
|
trackPanel->Refresh(false);
|
|
|
|
wxString message = ClipBoundaryMessage(results);
|
|
trackPanel->MessageForScreenReader(message);
|
|
}
|
|
}
|
|
|
|
MenuCommandHandler::FoundClip MenuCommandHandler::FindNextClip
|
|
(AudacityProject &project, const WaveTrack* wt, double t0, double t1)
|
|
{
|
|
(void)project;//Compiler food.
|
|
|
|
FoundClip result{};
|
|
result.waveTrack = wt;
|
|
const auto clips = wt->SortedClipArray();
|
|
|
|
t0 = AdjustForFindingStartTimes(clips, t0);
|
|
|
|
{
|
|
auto p = std::find_if(clips.begin(), clips.end(), [&] (const WaveClip* const& clip) {
|
|
return clip->GetStartTime() == t0; });
|
|
if (p != clips.end() && (*p)->GetEndTime() > t1) {
|
|
result.found = true;
|
|
result.startTime = (*p)->GetStartTime();
|
|
result.endTime = (*p)->GetEndTime();
|
|
result.index = std::distance(clips.begin(), p);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
{
|
|
auto p = std::find_if(clips.begin(), clips.end(), [&] (const WaveClip* const& clip) {
|
|
return clip->GetStartTime() > t0; });
|
|
if (p != clips.end()) {
|
|
result.found = true;
|
|
result.startTime = (*p)->GetStartTime();
|
|
result.endTime = (*p)->GetEndTime();
|
|
result.index = std::distance(clips.begin(), p);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
MenuCommandHandler::FoundClip MenuCommandHandler::FindPrevClip
|
|
(AudacityProject &project, const WaveTrack* wt, double t0, double t1)
|
|
{
|
|
(void)project;//Compiler food.
|
|
|
|
FoundClip result{};
|
|
result.waveTrack = wt;
|
|
const auto clips = wt->SortedClipArray();
|
|
|
|
t0 = AdjustForFindingStartTimes(clips, t0);
|
|
|
|
{
|
|
auto p = std::find_if(clips.begin(), clips.end(), [&] (const WaveClip* const& clip) {
|
|
return clip->GetStartTime() == t0; });
|
|
if (p != clips.end() && (*p)->GetEndTime() < t1) {
|
|
result.found = true;
|
|
result.startTime = (*p)->GetStartTime();
|
|
result.endTime = (*p)->GetEndTime();
|
|
result.index = std::distance(clips.begin(), p);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
{
|
|
auto p = std::find_if(clips.rbegin(), clips.rend(), [&] (const WaveClip* const& clip) {
|
|
return clip->GetStartTime() < t0; });
|
|
if (p != clips.rend()) {
|
|
result.found = true;
|
|
result.startTime = (*p)->GetStartTime();
|
|
result.endTime = (*p)->GetEndTime();
|
|
result.index = static_cast<int>(clips.size()) - 1 - std::distance(clips.rbegin(), p);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
int MenuCommandHandler::FindClips
|
|
(AudacityProject &project,
|
|
double t0, double t1, bool next, std::vector<FoundClip>& finalResults)
|
|
{
|
|
const auto tracks = project.GetTracks();
|
|
finalResults.clear();
|
|
|
|
bool anyWaveTracksSelected{ tracks->Selected< const WaveTrack >() };
|
|
|
|
// first search the tracks individually
|
|
|
|
std::vector<FoundClip> results;
|
|
|
|
int nTracksSearched = 0;
|
|
auto leaders = tracks->Leaders();
|
|
auto rangeLeaders = leaders.Filter<const WaveTrack>();
|
|
if (anyWaveTracksSelected)
|
|
rangeLeaders = rangeLeaders + &Track::GetSelected;
|
|
for (auto waveTrack : rangeLeaders) {
|
|
bool stereoAndDiff = ChannelsHaveDifferentClipBoundaries(waveTrack);
|
|
|
|
auto rangeChans = stereoAndDiff
|
|
? TrackList::Channels( waveTrack )
|
|
: TrackList::SingletonRange( waveTrack );
|
|
|
|
for ( auto wt : rangeChans ) {
|
|
auto result = next ? FindNextClip(project, wt, t0, t1) :
|
|
FindPrevClip(project, wt, t0, t1);
|
|
if (result.found) {
|
|
result.trackNum =
|
|
1 + std::distance( leaders.begin(), leaders.find( waveTrack ) );
|
|
result.channel = stereoAndDiff;
|
|
results.push_back(result);
|
|
}
|
|
}
|
|
|
|
nTracksSearched++;
|
|
}
|
|
|
|
|
|
if (results.size() > 0) {
|
|
// if any clips were found,
|
|
// find the clip or clips with the min/max start time
|
|
auto compareStart = [] (const FoundClip& a, const FoundClip& b)
|
|
{ return a.startTime < b.startTime; };
|
|
|
|
auto pStart = next ? std::min_element(results.begin(), results.end(), compareStart) :
|
|
std::max_element(results.begin(), results.end(), compareStart);
|
|
|
|
std::vector<FoundClip> resultsStartTime;
|
|
for ( auto &r : results )
|
|
if ( r.startTime == (*pStart).startTime )
|
|
resultsStartTime.push_back( r );
|
|
|
|
if (resultsStartTime.size() > 1) {
|
|
// more than one clip with same start time so
|
|
// find the clip or clips with the min/max end time
|
|
auto compareEnd = [] (const FoundClip& a, const FoundClip& b)
|
|
{ return a.endTime < b.endTime; };
|
|
|
|
auto pEnd = next ? std::min_element(resultsStartTime.begin(),
|
|
resultsStartTime.end(), compareEnd) :
|
|
std::max_element(resultsStartTime.begin(),
|
|
resultsStartTime.end(), compareEnd);
|
|
|
|
for ( auto &r : resultsStartTime )
|
|
if ( r.endTime == (*pEnd).endTime )
|
|
finalResults.push_back( r );
|
|
}
|
|
else {
|
|
finalResults = resultsStartTime;
|
|
}
|
|
}
|
|
|
|
return nTracksSearched; // can be used for screen reader messages if required
|
|
}
|
|
|
|
namespace {
|
|
bool TwoChannelsHaveSameBoundaries
|
|
( const WaveTrack *first, const WaveTrack *second )
|
|
{
|
|
bool sameClips = false;
|
|
auto& left = first->GetClips();
|
|
auto& right = second->GetClips();
|
|
if (left.size() == right.size()) {
|
|
sameClips = true;
|
|
for (unsigned int i = 0; i < left.size(); i++) {
|
|
if (left[i]->GetStartTime() != right[i]->GetStartTime() ||
|
|
left[i]->GetEndTime() != right[i]->GetEndTime()) {
|
|
sameClips = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return sameClips;
|
|
}
|
|
}
|
|
|
|
bool MenuCommandHandler::ChannelsHaveDifferentClipBoundaries(
|
|
const WaveTrack* wt)
|
|
{
|
|
// This is quadratic in the number of channels
|
|
auto channels = TrackList::Channels(wt);
|
|
while (!channels.empty()) {
|
|
auto channel = *channels.first++;
|
|
for (auto other : channels) {
|
|
if (!TwoChannelsHaveSameBoundaries(channel, other))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void MenuCommandHandler::OnSelectPrevClip(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
DoSelectClip(project, false);
|
|
}
|
|
|
|
void MenuCommandHandler::OnSelectNextClip(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
DoSelectClip(project, true);
|
|
}
|
|
|
|
void MenuCommandHandler::DoSelectClip(AudacityProject &project, bool next)
|
|
{
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
auto trackPanel = project.GetTrackPanel();
|
|
|
|
std::vector<FoundClip> results;
|
|
FindClips(project, selectedRegion.t0(),
|
|
selectedRegion.t1(), next, results);
|
|
|
|
if (results.size() > 0) {
|
|
// note that if there is more than one result, each has the same start
|
|
// and end time
|
|
double t0 = results[0].startTime;
|
|
double t1 = results[0].endTime;
|
|
selectedRegion.setTimes(t0, t1);
|
|
project.ModifyState(false);
|
|
trackPanel->ScrollIntoView(selectedRegion.t0());
|
|
trackPanel->Refresh(false);
|
|
|
|
// create and send message to screen reader
|
|
wxString message;
|
|
for (auto& result : results) {
|
|
auto longName = result.ComposeTrackName();
|
|
auto nClips = result.waveTrack->GetNumClips();
|
|
/* i18n-hint: in the string after this one,
|
|
first number identifies one of a sequence of clips,
|
|
last number counts the clips,
|
|
string names a track */
|
|
_("dummyStringOnSelectClip");
|
|
auto format = wxPLURAL(
|
|
"%d of %d clip %s",
|
|
"%d of %d clips %s",
|
|
nClips
|
|
);
|
|
auto str = wxString::Format( format, result.index + 1, nClips, longName );
|
|
|
|
if (message.empty())
|
|
message = str;
|
|
else
|
|
message = wxString::Format(_("%s, %s"), message, str);
|
|
}
|
|
trackPanel->MessageForScreenReader(message);
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::OnSelectCursorStoredCursor(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
auto isAudioActive = project.IsAudioActive();
|
|
|
|
if (mCursorPositionHasBeenStored) {
|
|
double cursorPositionCurrent = isAudioActive ? gAudioIO->GetStreamTime() : selectedRegion.t0();
|
|
selectedRegion.setTimes(std::min(cursorPositionCurrent, mCursorPositionStored),
|
|
std::max(cursorPositionCurrent, mCursorPositionStored));
|
|
|
|
project.ModifyState(false);
|
|
trackPanel->Refresh(false);
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::OnSelectSyncLockSel(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tracks = project.GetTracks();
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto mixerBoard = project.GetMixerBoard();
|
|
|
|
bool selected = false;
|
|
for (auto t : tracks->Any()
|
|
+ &Track::IsSyncLockSelected - &Track::IsSelected) {
|
|
t->SetSelected(true);
|
|
selected = true;
|
|
}
|
|
|
|
if (selected)
|
|
project.ModifyState(false);
|
|
|
|
trackPanel->Refresh(false);
|
|
if (mixerBoard)
|
|
mixerBoard->Refresh(false);
|
|
}
|
|
|
|
//
|
|
// View Menu
|
|
//
|
|
|
|
void MenuCommandHandler::OnZoomIn(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
project.ZoomInByFactor( 2.0 );
|
|
}
|
|
|
|
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 MenuCommandHandler::OnZoomOut(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
project.ZoomOutByFactor( 1 /2.0 );
|
|
}
|
|
|
|
|
|
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::OnZoomToggle(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto &viewInfo = project.GetViewInfo();
|
|
auto trackPanel = project.GetTrackPanel();
|
|
|
|
// const double origLeft = viewInfo.h;
|
|
// const double origWidth = GetScreenEndTime() - origLeft;
|
|
|
|
// Choose the zoom that is most different to the current zoom.
|
|
double Zoom1 = project.GetZoomOfPreset( TracksPrefs::Zoom1Choice() );
|
|
double Zoom2 = project.GetZoomOfPreset( TracksPrefs::Zoom2Choice() );
|
|
double Z = viewInfo.GetZoom();// Current Zoom.
|
|
double ChosenZoom = fabs(log(Zoom1 / Z)) > fabs(log( Z / Zoom2)) ? Zoom1:Zoom2;
|
|
|
|
project.Zoom(ChosenZoom);
|
|
trackPanel->Refresh(false);
|
|
// const double newWidth = GetScreenEndTime() - viewInfo.h;
|
|
// const double newh = origLeft + (origWidth - newWidth) / 2;
|
|
// TP_ScrollWindow(newh);
|
|
}
|
|
|
|
|
|
void MenuCommandHandler::OnZoomNormal(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto trackPanel = project.GetTrackPanel();
|
|
|
|
project.Zoom(ZoomInfo::GetDefaultZoom());
|
|
trackPanel->Refresh(false);
|
|
}
|
|
|
|
void MenuCommandHandler::OnZoomFit(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto &viewInfo = project.GetViewInfo();
|
|
auto tracks = project.GetTracks();
|
|
|
|
const double start = viewInfo.bScrollBeyondZero
|
|
? std::min(tracks->GetStartTime(), 0.0)
|
|
: 0;
|
|
|
|
project.Zoom( project.GetZoomOfToFit() );
|
|
project.TP_ScrollWindow(start);
|
|
}
|
|
|
|
void MenuCommandHandler::DoZoomFitV(AudacityProject &project)
|
|
{
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto tracks = project.GetTracks();
|
|
|
|
// Only nonminimized audio tracks will be resized
|
|
auto range = tracks->Any<AudioTrack>() - &Track::GetMinimized;
|
|
auto count = range.size();
|
|
if (count == 0)
|
|
return;
|
|
|
|
// Find total height to apportion
|
|
int height;
|
|
trackPanel->GetTracksUsableArea(NULL, &height);
|
|
height -= 28;
|
|
|
|
// The height of minimized and non-audio tracks cannot be apportioned
|
|
height -=
|
|
tracks->Any().sum( &Track::GetHeight ) - range.sum( &Track::GetHeight );
|
|
|
|
// Give each resized track the average of the remaining height
|
|
height = height / count;
|
|
height = std::max( (int)TrackInfo::MinimumTrackHeight(), height );
|
|
|
|
for (auto t : range)
|
|
t->SetHeight(height);
|
|
}
|
|
|
|
void MenuCommandHandler::OnZoomFitV(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
|
|
DoZoomFitV(project);
|
|
|
|
project.GetVerticalScrollBar().SetThumbPosition(0);
|
|
project.RedrawProject();
|
|
project.ModifyState(true);
|
|
}
|
|
|
|
void MenuCommandHandler::OnZoomSel(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
|
|
project.Zoom( project.GetZoomOfSelection() );
|
|
project.TP_ScrollWindow(selectedRegion.t0());
|
|
}
|
|
|
|
void MenuCommandHandler::OnGoSelStart(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto &viewInfo = project.GetViewInfo();
|
|
auto &selectedRegion = viewInfo.selectedRegion;
|
|
|
|
if (selectedRegion.isPoint())
|
|
return;
|
|
|
|
project.TP_ScrollWindow(
|
|
selectedRegion.t0() - ((project.GetScreenEndTime() - viewInfo.h) / 2));
|
|
}
|
|
|
|
void MenuCommandHandler::OnGoSelEnd(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto &viewInfo = project.GetViewInfo();
|
|
auto &selectedRegion = viewInfo.selectedRegion;
|
|
|
|
if (selectedRegion.isPoint())
|
|
return;
|
|
|
|
project.TP_ScrollWindow(
|
|
selectedRegion.t1() - ((project.GetScreenEndTime() - viewInfo.h) / 2));
|
|
}
|
|
|
|
void MenuCommandHandler::OnShowClipping(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto commandManager = project.GetCommandManager();
|
|
auto trackPanel = project.GetTrackPanel();
|
|
|
|
bool checked = !gPrefs->Read(wxT("/GUI/ShowClipping"), 0L);
|
|
gPrefs->Write(wxT("/GUI/ShowClipping"), checked);
|
|
gPrefs->Flush();
|
|
commandManager->Check(wxT("ShowClipping"), checked);
|
|
trackPanel->UpdatePrefs();
|
|
trackPanel->Refresh(false);
|
|
}
|
|
|
|
void MenuCommandHandler::OnShowExtraMenus(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto commandManager = project.GetCommandManager();
|
|
|
|
bool checked = !gPrefs->Read(wxT("/GUI/ShowExtraMenus"), 0L);
|
|
gPrefs->Write(wxT("/GUI/ShowExtraMenus"), checked);
|
|
gPrefs->Flush();
|
|
commandManager->Check(wxT("ShowExtraMenus"), checked);
|
|
RebuildAllMenuBars();
|
|
}
|
|
|
|
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::OnHistory(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
|
|
auto historyWindow = project.GetHistoryWindow(true);
|
|
historyWindow->Show();
|
|
historyWindow->Raise();
|
|
historyWindow->UpdateDisplay();
|
|
}
|
|
|
|
void MenuCommandHandler::OnKaraoke(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
|
|
auto lyricsWindow = project.GetLyricsWindow(true);
|
|
lyricsWindow->Show();
|
|
project.UpdateLyrics();
|
|
lyricsWindow->Raise();
|
|
}
|
|
|
|
void MenuCommandHandler::OnMixerBoard(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
|
|
auto mixerBoardFrame = project.GetMixerBoardFrame(true);
|
|
mixerBoardFrame->Show();
|
|
mixerBoardFrame->Raise();
|
|
mixerBoardFrame->SetFocus();
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
|
|
void MenuCommandHandler::OnShowTransportToolBar(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto toolManager = project.GetToolManager();
|
|
|
|
toolManager->ShowHide(TransportBarID);
|
|
MenuManager::ModifyToolbarMenus(project);
|
|
}
|
|
|
|
void MenuCommandHandler::OnShowDeviceToolBar(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto toolManager = project.GetToolManager();
|
|
|
|
toolManager->ShowHide( DeviceBarID );
|
|
MenuManager::ModifyToolbarMenus(project);
|
|
}
|
|
|
|
void MenuCommandHandler::OnShowEditToolBar(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto toolManager = project.GetToolManager();
|
|
|
|
toolManager->ShowHide( EditBarID );
|
|
MenuManager::ModifyToolbarMenus(project);
|
|
}
|
|
|
|
void MenuCommandHandler::OnShowMeterToolBar(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto toolManager = project.GetToolManager();
|
|
|
|
if( !toolManager->IsVisible( MeterBarID ) )
|
|
{
|
|
toolManager->Expose( PlayMeterBarID, false );
|
|
toolManager->Expose( RecordMeterBarID, false );
|
|
}
|
|
toolManager->ShowHide( MeterBarID );
|
|
MenuManager::ModifyToolbarMenus(project);
|
|
}
|
|
|
|
void MenuCommandHandler::OnShowRecordMeterToolBar(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto toolManager = project.GetToolManager();
|
|
|
|
if( !toolManager->IsVisible( RecordMeterBarID ) )
|
|
{
|
|
toolManager->Expose( MeterBarID, false );
|
|
}
|
|
toolManager->ShowHide( RecordMeterBarID );
|
|
MenuManager::ModifyToolbarMenus(project);
|
|
}
|
|
|
|
void MenuCommandHandler::OnShowPlayMeterToolBar(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto toolManager = project.GetToolManager();
|
|
|
|
if( !toolManager->IsVisible( PlayMeterBarID ) )
|
|
{
|
|
toolManager->Expose( MeterBarID, false );
|
|
}
|
|
|
|
toolManager->ShowHide( PlayMeterBarID );
|
|
MenuManager::ModifyToolbarMenus(project);
|
|
}
|
|
|
|
void MenuCommandHandler::OnShowMixerToolBar(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto toolManager = project.GetToolManager();
|
|
|
|
toolManager->ShowHide( MixerBarID );
|
|
MenuManager::ModifyToolbarMenus(project);
|
|
}
|
|
|
|
void MenuCommandHandler::OnShowScrubbingToolBar(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto toolManager = project.GetToolManager();
|
|
|
|
toolManager->ShowHide( ScrubbingBarID );
|
|
MenuManager::ModifyToolbarMenus(project);
|
|
}
|
|
|
|
void MenuCommandHandler::OnShowSelectionToolBar(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto toolManager = project.GetToolManager();
|
|
|
|
toolManager->ShowHide( SelectionBarID );
|
|
MenuManager::ModifyToolbarMenus(project);
|
|
}
|
|
|
|
#ifdef EXPERIMENTAL_SPECTRAL_EDITING
|
|
void MenuCommandHandler::OnShowSpectralSelectionToolBar(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto toolManager = project.GetToolManager();
|
|
|
|
toolManager->ShowHide( SpectralSelectionBarID );
|
|
MenuManager::ModifyToolbarMenus(project);
|
|
}
|
|
#endif
|
|
|
|
void MenuCommandHandler::OnShowToolsToolBar(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto toolManager = project.GetToolManager();
|
|
|
|
toolManager->ShowHide( ToolsBarID );
|
|
MenuManager::ModifyToolbarMenus(project);
|
|
}
|
|
|
|
void MenuCommandHandler::OnShowTranscriptionToolBar(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto toolManager = project.GetToolManager();
|
|
|
|
toolManager->ShowHide( TranscriptionBarID );
|
|
MenuManager::ModifyToolbarMenus(project);
|
|
}
|
|
|
|
void MenuCommandHandler::OnResetToolBars(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto toolManager = project.GetToolManager();
|
|
|
|
toolManager->Reset();
|
|
MenuManager::ModifyToolbarMenus(project);
|
|
}
|
|
|
|
#if defined(EXPERIMENTAL_EFFECTS_RACK)
|
|
void MenuCommandHandler::OnShowEffectsRack(const &WXUNUSED(context) )
|
|
{
|
|
EffectManager::Get().ShowRack();
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// Project Menu
|
|
//
|
|
|
|
void MenuCommandHandler::OnImport(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
|
|
// An import trigger for the alias missing dialog might not be intuitive, but
|
|
// this serves to track the file if the users zooms in and such.
|
|
wxGetApp().SetMissingAliasedFileWarningShouldShow(true);
|
|
|
|
wxArrayString selectedFiles = project.ShowOpenDialog(wxT(""));
|
|
if (selectedFiles.GetCount() == 0) {
|
|
gPrefs->Write(wxT("/LastOpenType"),wxT(""));
|
|
gPrefs->Flush();
|
|
return;
|
|
}
|
|
|
|
// PRL: This affects FFmpegImportPlugin::Open which resets the preference
|
|
// to false. Should it also be set to true on other paths that reach
|
|
// AudacityProject::Import ?
|
|
gPrefs->Write(wxT("/NewImportingSession"), true);
|
|
|
|
//sort selected files by OD status. Load non OD first so user can edit asap.
|
|
//first sort selectedFiles.
|
|
selectedFiles.Sort(CompareNoCaseFileName);
|
|
ODManager::Pauser pauser;
|
|
|
|
auto cleanup = finally( [&] {
|
|
gPrefs->Write(wxT("/LastOpenType"),wxT(""));
|
|
|
|
gPrefs->Flush();
|
|
|
|
project.HandleResize(); // Adjust scrollers for NEW track sizes.
|
|
} );
|
|
|
|
for (size_t ff = 0; ff < selectedFiles.GetCount(); ff++) {
|
|
wxString fileName = selectedFiles[ff];
|
|
|
|
FileNames::UpdateDefaultPath(FileNames::Operation::Open, fileName);
|
|
|
|
project.Import(fileName);
|
|
}
|
|
|
|
project.ZoomAfterImport(nullptr);
|
|
}
|
|
|
|
void MenuCommandHandler::OnImportLabels(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto trackFactory = project.GetTrackFactory();
|
|
auto tracks = project.GetTracks();
|
|
|
|
wxString fileName =
|
|
FileNames::SelectFile(FileNames::Operation::Open,
|
|
_("Select a text file containing labels"),
|
|
wxEmptyString, // Path
|
|
wxT(""), // Name
|
|
wxT(".txt"), // Extension
|
|
_("Text files (*.txt)|*.txt|All files|*"),
|
|
wxRESIZE_BORDER, // Flags
|
|
&project); // Parent
|
|
|
|
if (fileName != wxT("")) {
|
|
wxTextFile f;
|
|
|
|
f.Open(fileName);
|
|
if (!f.IsOpened()) {
|
|
AudacityMessageBox(
|
|
wxString::Format( _("Could not open file: %s"), fileName ) );
|
|
return;
|
|
}
|
|
|
|
auto newTrack = trackFactory->NewLabelTrack();
|
|
wxString sTrackName;
|
|
wxFileName::SplitPath(fileName, NULL, NULL, &sTrackName, NULL);
|
|
newTrack->SetName(sTrackName);
|
|
|
|
newTrack->Import(f);
|
|
|
|
project.SelectNone();
|
|
newTrack->SetSelected(true);
|
|
tracks->Add(std::move(newTrack));
|
|
|
|
project.PushState(wxString::
|
|
Format(_("Imported labels from '%s'"), fileName),
|
|
_("Import Labels"));
|
|
|
|
project.ZoomAfterImport(nullptr);
|
|
}
|
|
}
|
|
|
|
#ifdef USE_MIDI
|
|
void MenuCommandHandler::OnImportMIDI(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
|
|
wxString fileName = FileNames::SelectFile(FileNames::Operation::Open,
|
|
_("Select a MIDI file"),
|
|
wxEmptyString, // Path
|
|
wxT(""), // Name
|
|
wxT(""), // Extension
|
|
_("MIDI and Allegro files (*.mid;*.midi;*.gro)|*.mid;*.midi;*.gro|MIDI files (*.mid;*.midi)|*.mid;*.midi|Allegro files (*.gro)|*.gro|All files|*"),
|
|
wxRESIZE_BORDER, // Flags
|
|
&project); // Parent
|
|
|
|
if (fileName != wxT(""))
|
|
DoImportMIDI(&project, fileName);
|
|
}
|
|
|
|
AudacityProject *MenuCommandHandler::DoImportMIDI(
|
|
AudacityProject *pProject, const wxString &fileName)
|
|
{
|
|
auto tracks = pProject->GetTracks();
|
|
|
|
AudacityProject *pNewProject {};
|
|
if ( !pProject )
|
|
pProject = pNewProject = CreateNewAudacityProject();
|
|
auto cleanup = finally( [&] { if ( pNewProject ) pNewProject->Close(true); } );
|
|
|
|
auto newTrack = pProject->GetTrackFactory()->NewNoteTrack();
|
|
|
|
if (::ImportMIDI(fileName, newTrack.get())) {
|
|
|
|
pProject->SelectNone();
|
|
auto pTrack = tracks->Add(std::move(newTrack));
|
|
pTrack->SetSelected(true);
|
|
|
|
pProject->PushState(wxString::Format(_("Imported MIDI from '%s'"),
|
|
fileName), _("Import MIDI"));
|
|
|
|
pProject->ZoomAfterImport(pTrack);
|
|
pNewProject = nullptr;
|
|
|
|
wxGetApp().AddFileToHistory(fileName);
|
|
|
|
return pProject;
|
|
}
|
|
else
|
|
return nullptr;
|
|
}
|
|
#endif // USE_MIDI
|
|
|
|
void MenuCommandHandler::OnImportRaw(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto trackFactory = project.GetTrackFactory();
|
|
|
|
wxString fileName =
|
|
FileNames::SelectFile(FileNames::Operation::Open,
|
|
_("Select any uncompressed audio file"),
|
|
wxEmptyString, // Path
|
|
wxT(""), // Name
|
|
wxT(""), // Extension
|
|
_("All files|*"),
|
|
wxRESIZE_BORDER, // Flags
|
|
&project); // Parent
|
|
|
|
if (fileName == wxT(""))
|
|
return;
|
|
|
|
TrackHolders newTracks;
|
|
|
|
::ImportRaw(&project, fileName, trackFactory, newTracks);
|
|
|
|
if (newTracks.size() <= 0)
|
|
return;
|
|
|
|
project.AddImportedTracks(fileName, std::move(newTracks));
|
|
project.HandleResize(); // Adjust scrollers for NEW track sizes.
|
|
}
|
|
|
|
void MenuCommandHandler::OnEditMetadata(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
(void)DoEditMetadata( project,
|
|
_("Edit Metadata Tags"), _("Metadata Tags"), true);
|
|
}
|
|
|
|
bool MenuCommandHandler::DoEditMetadata
|
|
(AudacityProject &project,
|
|
const wxString &title, const wxString &shortUndoDescription, bool force)
|
|
{
|
|
auto tags = project.GetTags();
|
|
|
|
// Back up my tags
|
|
auto newTags = tags->Duplicate();
|
|
|
|
if (newTags->ShowEditDialog(&project, title, force)) {
|
|
if (*tags != *newTags) {
|
|
// Commit the change to project state only now.
|
|
project.SetTags( newTags );
|
|
project.PushState(title, shortUndoDescription);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
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::OnSelectionSave(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
|
|
mRegionSave = selectedRegion;
|
|
}
|
|
|
|
void MenuCommandHandler::OnCursorPositionStore(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
auto isAudioActive = project.IsAudioActive();
|
|
|
|
mCursorPositionStored =
|
|
isAudioActive ? gAudioIO->GetStreamTime() : selectedRegion.t0();
|
|
mCursorPositionHasBeenStored = true;
|
|
}
|
|
|
|
void MenuCommandHandler::OnSelectionRestore(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
auto trackPanel = project.GetTrackPanel();
|
|
|
|
if ((mRegionSave.t0() == 0.0) &&
|
|
(mRegionSave.t1() == 0.0))
|
|
return;
|
|
|
|
selectedRegion = mRegionSave;
|
|
|
|
project.ModifyState(false);
|
|
|
|
trackPanel->Refresh(false);
|
|
}
|
|
|
|
void MenuCommandHandler::OnCursorTrackStart(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tracks = project.GetTracks();
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
|
|
double kWayOverToRight = std::numeric_limits<double>::max();
|
|
|
|
auto trackRange = tracks->Selected();
|
|
if (trackRange.empty())
|
|
// This should have been prevented by command manager
|
|
return;
|
|
|
|
// Range is surely nonempty now
|
|
auto minOffset = std::max( 0.0, trackRange.min( &Track::GetOffset ) );
|
|
|
|
if( minOffset >=
|
|
(kWayOverToRight * (1 - std::numeric_limits<double>::epsilon()) ))
|
|
return;
|
|
|
|
selectedRegion.setTimes(minOffset, minOffset);
|
|
project.ModifyState(false);
|
|
trackPanel->ScrollIntoView(selectedRegion.t0());
|
|
trackPanel->Refresh(false);
|
|
}
|
|
|
|
void MenuCommandHandler::OnCursorTrackEnd(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tracks = project.GetTracks();
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
|
|
double kWayOverToLeft = std::numeric_limits<double>::lowest();
|
|
|
|
auto trackRange = tracks->Selected();
|
|
if (trackRange.empty())
|
|
// This should have been prevented by command manager
|
|
return;
|
|
|
|
// Range is surely nonempty now
|
|
auto maxEndOffset = trackRange.max( &Track::GetEndTime );
|
|
|
|
if( maxEndOffset <
|
|
(kWayOverToLeft * (1 - std::numeric_limits<double>::epsilon()) ))
|
|
return;
|
|
|
|
selectedRegion.setTimes(maxEndOffset, maxEndOffset);
|
|
project.ModifyState(false);
|
|
trackPanel->ScrollIntoView(selectedRegion.t1());
|
|
trackPanel->Refresh(false);
|
|
}
|
|
|
|
void MenuCommandHandler::OnCursorSelStart(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
|
|
selectedRegion.collapseToT0();
|
|
project.ModifyState(false);
|
|
trackPanel->ScrollIntoView(selectedRegion.t0());
|
|
trackPanel->Refresh(false);
|
|
}
|
|
|
|
void MenuCommandHandler::OnCursorSelEnd(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
|
|
selectedRegion.collapseToT1();
|
|
project.ModifyState(false);
|
|
trackPanel->ScrollIntoView(selectedRegion.t1());
|
|
trackPanel->Refresh(false);
|
|
}
|
|
|
|
MenuCommandHandler::FoundClipBoundary MenuCommandHandler::FindNextClipBoundary
|
|
(const WaveTrack* wt, double time)
|
|
{
|
|
FoundClipBoundary result{};
|
|
result.waveTrack = wt;
|
|
const auto clips = wt->SortedClipArray();
|
|
double timeStart = AdjustForFindingStartTimes(clips, time);
|
|
double timeEnd = AdjustForFindingEndTimes(clips, time);
|
|
|
|
auto pStart = std::find_if(clips.begin(), clips.end(), [&] (const WaveClip* const& clip) {
|
|
return clip->GetStartTime() > timeStart; });
|
|
auto pEnd = std::find_if(clips.begin(), clips.end(), [&] (const WaveClip* const& clip) {
|
|
return clip->GetEndTime() > timeEnd; });
|
|
|
|
if (pStart != clips.end() && pEnd != clips.end()) {
|
|
if ((*pEnd)->SharesBoundaryWithNextClip(*pStart)) {
|
|
// boundary between two clips which are immediately next to each other.
|
|
result.nFound = 2;
|
|
result.time = (*pEnd)->GetEndTime();
|
|
result.index1 = std::distance(clips.begin(), pEnd);
|
|
result.clipStart1 = false;
|
|
result.index2 = std::distance(clips.begin(), pStart);
|
|
result.clipStart2 = true;
|
|
}
|
|
else if ((*pStart)->GetStartTime() < (*pEnd)->GetEndTime()) {
|
|
result.nFound = 1;
|
|
result.time = (*pStart)->GetStartTime();
|
|
result.index1 = std::distance(clips.begin(), pStart);
|
|
result.clipStart1 = true;
|
|
}
|
|
else {
|
|
result.nFound = 1;
|
|
result.time = (*pEnd)->GetEndTime();
|
|
result.index1 = std::distance(clips.begin(), pEnd);
|
|
result.clipStart1 = false;
|
|
}
|
|
}
|
|
else if (pEnd != clips.end()) {
|
|
result.nFound = 1;
|
|
result.time = (*pEnd)->GetEndTime();
|
|
result.index1 = std::distance(clips.begin(), pEnd);
|
|
result.clipStart1 = false;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
MenuCommandHandler::FoundClipBoundary MenuCommandHandler::FindPrevClipBoundary(const WaveTrack* wt, double time)
|
|
{
|
|
FoundClipBoundary result{};
|
|
result.waveTrack = wt;
|
|
const auto clips = wt->SortedClipArray();
|
|
double timeStart = AdjustForFindingStartTimes(clips, time);
|
|
double timeEnd = AdjustForFindingEndTimes(clips, time);
|
|
|
|
auto pStart = std::find_if(clips.rbegin(), clips.rend(), [&] (const WaveClip* const& clip) {
|
|
return clip->GetStartTime() < timeStart; });
|
|
auto pEnd = std::find_if(clips.rbegin(), clips.rend(), [&] (const WaveClip* const& clip) {
|
|
return clip->GetEndTime() < timeEnd; });
|
|
|
|
if (pStart != clips.rend() && pEnd != clips.rend()) {
|
|
if ((*pEnd)->SharesBoundaryWithNextClip(*pStart)) {
|
|
// boundary between two clips which are immediately next to each other.
|
|
result.nFound = 2;
|
|
result.time = (*pStart)->GetStartTime();
|
|
result.index1 = static_cast<int>(clips.size()) - 1 - std::distance(clips.rbegin(), pStart);
|
|
result.clipStart1 = true;
|
|
result.index2 = static_cast<int>(clips.size()) - 1 - std::distance(clips.rbegin(), pEnd);
|
|
result.clipStart2 = false;
|
|
}
|
|
else if ((*pStart)->GetStartTime() > (*pEnd)->GetEndTime()) {
|
|
result.nFound = 1;
|
|
result.time = (*pStart)->GetStartTime();
|
|
result.index1 = static_cast<int>(clips.size()) - 1 - std::distance(clips.rbegin(), pStart);
|
|
result.clipStart1 = true;
|
|
}
|
|
else {
|
|
result.nFound = 1;
|
|
result.time = (*pEnd)->GetEndTime();
|
|
result.index1 = static_cast<int>(clips.size()) - 1 - std::distance(clips.rbegin(), pEnd);
|
|
result.clipStart1 = false;
|
|
}
|
|
}
|
|
else if (pStart != clips.rend()) {
|
|
result.nFound = 1;
|
|
result.time = (*pStart)->GetStartTime();
|
|
result.index1 = static_cast<int>(clips.size()) - 1 - std::distance(clips.rbegin(), pStart);
|
|
result.clipStart1 = true;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// When two clips are immediately next to each other, the GetEndTime() of the first clip and the
|
|
// GetStartTime() of the second clip may not be exactly equal due to rounding errors. When searching
|
|
// for the next/prev start time from a given time, the following function adjusts that given time if
|
|
// necessary to take this into account. If the given time is the end time of the first of two clips which
|
|
// are next to each other, then the given time is changed to the start time of the second clip.
|
|
// This ensures that the correct next/prev start time is found.
|
|
double MenuCommandHandler::AdjustForFindingStartTimes(const std::vector<const WaveClip*> & clips, double time)
|
|
{
|
|
auto q = std::find_if(clips.begin(), clips.end(), [&] (const WaveClip* const& clip) {
|
|
return clip->GetEndTime() == time; });
|
|
if (q != clips.end() && q + 1 != clips.end() &&
|
|
(*q)->SharesBoundaryWithNextClip(*(q+1))) {
|
|
time = (*(q+1))->GetStartTime();
|
|
}
|
|
|
|
return time;
|
|
}
|
|
|
|
// When two clips are immediately next to each other, the GetEndTime() of the first clip and the
|
|
// GetStartTime() of the second clip may not be exactly equal due to rounding errors. When searching
|
|
// for the next/prev end time from a given time, the following function adjusts that given time if
|
|
// necessary to take this into account. If the given time is the start time of the second of two clips which
|
|
// are next to each other, then the given time is changed to the end time of the first clip.
|
|
// This ensures that the correct next/prev end time is found.
|
|
double MenuCommandHandler::AdjustForFindingEndTimes(const std::vector<const WaveClip*>& clips, double time)
|
|
{
|
|
auto q = std::find_if(clips.begin(), clips.end(), [&] (const WaveClip* const& clip) {
|
|
return clip->GetStartTime() == time; });
|
|
if (q != clips.end() && q != clips.begin() &&
|
|
(*(q - 1))->SharesBoundaryWithNextClip(*q)) {
|
|
time = (*(q-1))->GetEndTime();
|
|
}
|
|
|
|
return time;
|
|
}
|
|
|
|
int MenuCommandHandler::FindClipBoundaries
|
|
(AudacityProject &project,
|
|
double time, bool next, std::vector<FoundClipBoundary>& finalResults)
|
|
{
|
|
auto tracks = project.GetTracks();
|
|
finalResults.clear();
|
|
|
|
bool anyWaveTracksSelected{ tracks->Selected< const WaveTrack >() };
|
|
|
|
|
|
// first search the tracks individually
|
|
|
|
std::vector<FoundClipBoundary> results;
|
|
|
|
int nTracksSearched = 0;
|
|
auto leaders = tracks->Leaders();
|
|
auto rangeLeaders = leaders.Filter<const WaveTrack>();
|
|
if (anyWaveTracksSelected)
|
|
rangeLeaders = rangeLeaders + &Track::GetSelected;
|
|
for (auto waveTrack : rangeLeaders) {
|
|
bool stereoAndDiff = ChannelsHaveDifferentClipBoundaries(waveTrack);
|
|
|
|
auto rangeChan = stereoAndDiff
|
|
? TrackList::Channels( waveTrack )
|
|
: TrackList::SingletonRange(waveTrack);
|
|
|
|
for (auto wt : rangeChan) {
|
|
auto result = next ? FindNextClipBoundary(wt, time) :
|
|
FindPrevClipBoundary(wt, time);
|
|
if (result.nFound > 0) {
|
|
result.trackNum =
|
|
1 + std::distance( leaders.begin(), leaders.find( waveTrack ) );
|
|
result.channel = stereoAndDiff;
|
|
results.push_back(result);
|
|
}
|
|
}
|
|
|
|
nTracksSearched++;
|
|
}
|
|
|
|
|
|
if (results.size() > 0) {
|
|
// If any clip boundaries were found
|
|
// find the clip boundary or boundaries with the min/max time
|
|
auto compare = [] (const FoundClipBoundary& a, const FoundClipBoundary&b)
|
|
{ return a.time < b.time; };
|
|
|
|
auto p = next ? min_element(results.begin(), results.end(), compare ) :
|
|
max_element(results.begin(), results.end(), compare);
|
|
|
|
for ( auto &r : results )
|
|
if ( r.time == (*p).time )
|
|
finalResults.push_back( r );
|
|
}
|
|
|
|
return nTracksSearched; // can be used for screen reader messages if required
|
|
}
|
|
|
|
|
|
void MenuCommandHandler::OnCursorNextClipBoundary(const CommandContext &context)
|
|
{
|
|
AudacityProject &project = context.project;
|
|
|
|
DoCursorClipBoundary(project, true);
|
|
}
|
|
|
|
void MenuCommandHandler::OnCursorPrevClipBoundary(const CommandContext &context)
|
|
{
|
|
AudacityProject &project = context.project;
|
|
|
|
DoCursorClipBoundary(project, false);
|
|
}
|
|
|
|
void MenuCommandHandler::DoCursorClipBoundary
|
|
(AudacityProject &project, bool next)
|
|
{
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
auto trackPanel = project.GetTrackPanel();
|
|
|
|
std::vector<FoundClipBoundary> results;
|
|
FindClipBoundaries(project, next ? selectedRegion.t1() :
|
|
selectedRegion.t0(), next, results);
|
|
|
|
if (results.size() > 0) {
|
|
// note that if there is more than one result, each has the same time value.
|
|
double time = results[0].time;
|
|
selectedRegion.setTimes(time, time);
|
|
project.ModifyState(false);
|
|
trackPanel->ScrollIntoView(selectedRegion.t0());
|
|
trackPanel->Refresh(false);
|
|
|
|
wxString message = ClipBoundaryMessage(results);
|
|
trackPanel->MessageForScreenReader(message);
|
|
}
|
|
}
|
|
|
|
wxString MenuCommandHandler::FoundTrack::ComposeTrackName() const
|
|
{
|
|
auto name = waveTrack->GetName();
|
|
auto shortName = name == waveTrack->GetDefaultName()
|
|
/* i18n-hint: compose a name identifying an unnamed track by number */
|
|
? wxString::Format( _("Track %d"), trackNum )
|
|
: name;
|
|
auto longName = shortName;
|
|
if (channel) {
|
|
// TODO: more-than-two-channels-message
|
|
if ( waveTrack->IsLeader() )
|
|
/* i18n-hint: given the name of a track, specify its left channel */
|
|
longName = wxString::Format(_("%s left"), shortName);
|
|
else
|
|
/* i18n-hint: given the name of a track, specify its right channel */
|
|
longName = wxString::Format(_("%s right"), shortName);
|
|
}
|
|
return longName;
|
|
}
|
|
|
|
// for clip boundary commands, create a message for screen readers
|
|
wxString MenuCommandHandler::ClipBoundaryMessage(const std::vector<FoundClipBoundary>& results)
|
|
{
|
|
wxString message;
|
|
for (auto& result : results) {
|
|
|
|
auto longName = result.ComposeTrackName();
|
|
|
|
wxString str;
|
|
auto nClips = result.waveTrack->GetNumClips();
|
|
if (result.nFound < 2) {
|
|
/* i18n-hint: in the string after this one,
|
|
First %s is replaced with the noun "start" or "end"
|
|
identifying one end of a clip,
|
|
first number gives the position of that clip in a sequence
|
|
of clips,
|
|
last number counts all clips,
|
|
and the last string is the name of the track containing the clips.
|
|
*/
|
|
_("dummyStringClipBoundaryMessage");
|
|
auto format = wxPLURAL(
|
|
"%s %d of %d clip %s",
|
|
"%s %d of %d clips %s",
|
|
nClips
|
|
);
|
|
str = wxString::Format(format,
|
|
result.clipStart1 ? _("start") : _("end"),
|
|
result.index1 + 1,
|
|
nClips,
|
|
longName
|
|
);
|
|
}
|
|
else {
|
|
/* i18n-hint: in the string after this one,
|
|
First two %s are each replaced with the noun "start"
|
|
or with "end", identifying and end of a clip,
|
|
first and second numbers give the position of those clips in
|
|
a seqeunce of clips,
|
|
last number counts all clips,
|
|
and the last string is the name of the track containing the clips.
|
|
*/
|
|
_("dummyStringClipBoundaryMessageLong");
|
|
auto format = wxPLURAL(
|
|
"%s %d and %s %d of %d clip %s",
|
|
"%s %d and %s %d of %d clips %s",
|
|
nClips
|
|
);
|
|
str = wxString::Format(format,
|
|
result.clipStart1 ? _("start") : _("end"),
|
|
result.index1 + 1,
|
|
result.clipStart2 ? _("start") : _("end"),
|
|
result.index2 + 1,
|
|
nClips,
|
|
longName
|
|
);
|
|
}
|
|
|
|
if (message.empty())
|
|
message = str;
|
|
else
|
|
message = wxString::Format(_("%s, %s"), message, str);
|
|
}
|
|
|
|
return message;
|
|
}
|
|
|
|
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 >= GetMenuManager(project).mAlignLabelsCount) { // 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) {
|
|
OnZoomFit(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 + GetMenuManager(project).mAlignLabelsCount, 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::OnTimerRecord(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto &undoManager = *project.GetUndoManager();
|
|
|
|
// MY: Due to improvements in how Timer Recording saves and/or exports
|
|
// it is now safer to disable Timer Recording when there is more than
|
|
// one open project.
|
|
if (AudacityProject::GetOpenProjectCount() > 1) {
|
|
AudacityMessageBox(_("Timer Recording cannot be used with more than one open project.\n\nPlease close any additional projects and try again."),
|
|
_("Timer Recording"),
|
|
wxICON_INFORMATION | wxOK);
|
|
return;
|
|
}
|
|
|
|
// MY: If the project has unsaved changes then we no longer allow access
|
|
// to Timer Recording. This decision has been taken as the safest approach
|
|
// preventing issues surrounding "dirty" projects when Automatic Save/Export
|
|
// is used in Timer Recording.
|
|
if ((undoManager.UnsavedChanges()) &&
|
|
(project.ProjectHasTracks() || project.EmptyCanBeDirty())) {
|
|
AudacityMessageBox(_("Timer Recording cannot be used while you have unsaved changes.\n\nPlease save or close this project and try again."),
|
|
_("Timer Recording"),
|
|
wxICON_INFORMATION | wxOK);
|
|
return;
|
|
}
|
|
// We use this variable to display "Current Project" in the Timer Recording save project field
|
|
bool bProjectSaved = project.IsProjectSaved();
|
|
|
|
//we break the prompting and waiting dialogs into two sections
|
|
//because they both give the user a chance to click cancel
|
|
//and therefore remove the newly inserted track.
|
|
|
|
TimerRecordDialog dialog(&project, bProjectSaved); /* parent, project saved? */
|
|
int modalResult = dialog.ShowModal();
|
|
if (modalResult == wxID_CANCEL)
|
|
{
|
|
// Cancelled before recording - don't need to do anyting.
|
|
}
|
|
else
|
|
{
|
|
// Timer Record should not record into a selection.
|
|
bool bPreferNewTrack;
|
|
gPrefs->Read("/GUI/PreferNewTrackRecord",&bPreferNewTrack, false);
|
|
if (bPreferNewTrack) {
|
|
project.Rewind(false);
|
|
} else {
|
|
project.SkipEnd(false);
|
|
}
|
|
|
|
int iTimerRecordingOutcome = dialog.RunWaitDialog();
|
|
switch (iTimerRecordingOutcome) {
|
|
case POST_TIMER_RECORD_CANCEL_WAIT:
|
|
// Canceled on the wait dialog
|
|
project.RollbackState();
|
|
break;
|
|
case POST_TIMER_RECORD_CANCEL:
|
|
// RunWaitDialog() shows the "wait for start" as well as "recording" dialog
|
|
// if it returned POST_TIMER_RECORD_CANCEL it means the user cancelled while the recording, so throw out the fresh track.
|
|
// However, we can't undo it here because the PushState() is called in TrackPanel::OnTimer(),
|
|
// which is blocked by this function.
|
|
// so instead we mark a flag to undo it there.
|
|
project.SetTimerRecordCancelled();
|
|
break;
|
|
case POST_TIMER_RECORD_NOTHING:
|
|
// No action required
|
|
break;
|
|
case POST_TIMER_RECORD_CLOSE:
|
|
wxTheApp->CallAfter( []{ QuitAudacity(); } );
|
|
break;
|
|
case POST_TIMER_RECORD_RESTART:
|
|
// Restart System
|
|
#ifdef __WINDOWS__
|
|
system("shutdown /r /f /t 30");
|
|
#endif
|
|
break;
|
|
case POST_TIMER_RECORD_SHUTDOWN:
|
|
// Shutdown System
|
|
#ifdef __WINDOWS__
|
|
system("shutdown /s /f /t 30");
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::OnSoundActivated(const CommandContext &context)
|
|
{
|
|
AudacityProject &project = context.project;
|
|
|
|
SoundActivatedRecord dialog(&project /* parent */ );
|
|
dialog.ShowModal();
|
|
}
|
|
|
|
void MenuCommandHandler::OnRescanDevices(const CommandContext &WXUNUSED(context) )
|
|
{
|
|
DeviceManager::Instance()->Rescan();
|
|
}
|
|
|
|
int MenuCommandHandler::DialogForLabelName(
|
|
AudacityProject &project, const wxString& initialValue, wxString& value)
|
|
{
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto &viewInfo = project.GetViewInfo();
|
|
|
|
wxPoint position = trackPanel->FindTrackRect(trackPanel->GetFocusedTrack(), false).GetBottomLeft();
|
|
// the start of the text in the text box will be roughly in line with Audacity's edit cursor
|
|
position.x += trackPanel->GetLabelWidth() + viewInfo.TimeToPosition(viewInfo.selectedRegion.t0()) - 40;
|
|
position.y += 2; // just below the bottom of the track
|
|
position = trackPanel->ClientToScreen(position);
|
|
AudacityTextEntryDialog dialog{ &project,
|
|
_("Name:"),
|
|
_("New label"),
|
|
initialValue,
|
|
wxOK | wxCANCEL,
|
|
position };
|
|
|
|
// keep the dialog within Audacity's window, so that the dialog is always fully visible
|
|
wxRect dialogScreenRect = dialog.GetScreenRect();
|
|
wxRect projScreenRect = project.GetScreenRect();
|
|
wxPoint max = projScreenRect.GetBottomRight() + wxPoint{ -dialogScreenRect.width, -dialogScreenRect.height };
|
|
if (dialogScreenRect.x > max.x) {
|
|
position.x = max.x;
|
|
dialog.Move(position);
|
|
}
|
|
if (dialogScreenRect.y > max.y) {
|
|
position.y = max.y;
|
|
dialog.Move(position);
|
|
}
|
|
|
|
dialog.SetInsertionPointEnd(); // because, by default, initial text is selected
|
|
int status = dialog.ShowModal();
|
|
if (status != wxID_CANCEL) {
|
|
value = dialog.GetValue();
|
|
value.Trim(true).Trim(false);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
#ifdef EXPERIMENTAL_PUNCH_AND_ROLL
|
|
void MenuCommandHandler::OnPunchAndRoll(const CommandContext &context)
|
|
{
|
|
AudacityProject &project = context.project;
|
|
auto &viewInfo = project.GetViewInfo();
|
|
|
|
static const auto url =
|
|
wxT("Punch_and_Roll_Record#Using_Punch_and_Roll_Record");
|
|
|
|
if (gAudioIO->IsBusy())
|
|
return;
|
|
|
|
// Ignore all but left edge of the selection.
|
|
viewInfo.selectedRegion.collapseToT0();
|
|
double t1 = std::max(0.0, viewInfo.selectedRegion.t1());
|
|
|
|
// Decide which tracks to record in.
|
|
auto pBar = project.GetControlToolBar();
|
|
auto tracks = pBar->ChooseExistingRecordingTracks(project, true);
|
|
if (tracks.empty()) {
|
|
int recordingChannels =
|
|
std::max(0L, gPrefs->Read(wxT("/AudioIO/RecordChannels"), 2));
|
|
auto message =
|
|
(recordingChannels == 1)
|
|
? _("Please select in a mono track.")
|
|
: (recordingChannels == 2)
|
|
? _("Please select in a stereo track.")
|
|
: wxString::Format(
|
|
_("Please select at least %d channels."), recordingChannels);
|
|
ShowErrorDialog(&project, _("Error"), message, url);
|
|
return;
|
|
}
|
|
|
|
// Delete the portion of the target tracks right of the selection, but first,
|
|
// remember a part of the deletion for crossfading with the new recording.
|
|
// We may also adjust the starting point leftward if it is too close to the
|
|
// end of the track, so that at least some nonzero crossfade data can be
|
|
// taken.
|
|
PRCrossfadeData crossfadeData;
|
|
const double crossFadeDuration = std::max(0.0,
|
|
gPrefs->Read(AUDIO_ROLL_CROSSFADE_KEY, DEFAULT_ROLL_CROSSFADE_MS)
|
|
/ 1000.0
|
|
);
|
|
|
|
// The test for t1 == 0.0 stops punch and roll deleting everything where the
|
|
// selection is at zero. There wouldn't be any cued audio to play in
|
|
// that case, so a normal record, not a punch and roll, is called for.
|
|
bool error = (t1 == 0.0);
|
|
|
|
double newt1 = t1;
|
|
for (const auto &wt : tracks) {
|
|
sampleCount testSample(floor(t1 * wt->GetRate()));
|
|
auto clip = wt->GetClipAtSample(testSample);
|
|
if (!clip)
|
|
// Bug 1890 (an enhancement request)
|
|
// Try again, a little to the left.
|
|
// Subtract 10 to allow a selection exactly at or slightly after the end time
|
|
clip = wt->GetClipAtSample(testSample - 10);
|
|
if (!clip)
|
|
error = true;
|
|
else {
|
|
// May adjust t1 left
|
|
// Let's ignore the possibilty of a clip even shorter than the
|
|
// crossfade duration!
|
|
newt1 = std::min(newt1, clip->GetEndTime() - crossFadeDuration);
|
|
}
|
|
}
|
|
|
|
if (error) {
|
|
auto message = _("Please select a time within a clip.");
|
|
ShowErrorDialog(&project, _("Error"), message, url);
|
|
return;
|
|
}
|
|
|
|
t1 = newt1;
|
|
for (const auto &wt : tracks) {
|
|
const auto endTime = wt->GetEndTime();
|
|
const auto duration = std::max(0.0, std::min(crossFadeDuration, endTime - t1));
|
|
const size_t getLen = floor(duration * wt->GetRate());
|
|
std::vector<float> data(getLen);
|
|
if (getLen > 0) {
|
|
float *const samples = data.data();
|
|
const sampleCount pos = wt->TimeToLongSamples(t1);
|
|
wt->Get((samplePtr)samples, floatSample, pos, getLen);
|
|
}
|
|
crossfadeData.push_back(std::move(data));
|
|
}
|
|
|
|
// Change tracks only after passing the error checks above
|
|
for (const auto &wt : tracks) {
|
|
wt->Clear(t1, wt->GetEndTime());
|
|
}
|
|
|
|
// Choose the tracks for playback.
|
|
TransportTracks transportTracks;
|
|
const auto duplex = ControlToolBar::UseDuplex();
|
|
if (duplex)
|
|
// play all
|
|
transportTracks = GetAllPlaybackTracks(*project.GetTracks(), false, true);
|
|
else
|
|
// play recording tracks only
|
|
std::copy(tracks.begin(), tracks.end(), std::back_inserter(transportTracks.playbackTracks));
|
|
|
|
// Unlike with the usual recording, a track may be chosen both for playback and recording.
|
|
transportTracks.captureTracks = std::move(tracks);
|
|
|
|
// Try to start recording
|
|
AudioIOStartStreamOptions options(project.GetDefaultPlayOptions());
|
|
options.preRoll = std::max(0L,
|
|
gPrefs->Read(AUDIO_PRE_ROLL_KEY, DEFAULT_PRE_ROLL_SECONDS));
|
|
options.pCrossfadeData = &crossfadeData;
|
|
bool success = project.GetControlToolBar()->DoRecord(project,
|
|
transportTracks,
|
|
t1, DBL_MAX,
|
|
false, // altAppearance
|
|
options);
|
|
|
|
if (success)
|
|
// Undo state will get pushed elsewhere, when record finishes
|
|
;
|
|
else
|
|
// Roll back the deletions
|
|
project.RollbackState();
|
|
}
|
|
#endif
|
|
|
|
int MenuCommandHandler::DoAddLabel(
|
|
AudacityProject &project, const SelectedRegion ®ion, bool preserveFocus)
|
|
{
|
|
auto tracks = project.GetTracks();
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto trackFactory = project.GetTrackFactory();
|
|
|
|
wxString title; // of label
|
|
|
|
bool useDialog;
|
|
gPrefs->Read(wxT("/GUI/DialogForNameNewLabel"), &useDialog, false);
|
|
if (useDialog) {
|
|
if (DialogForLabelName(project, wxEmptyString, title) == wxID_CANCEL)
|
|
return -1; // index
|
|
}
|
|
|
|
// If the focused track is a label track, use that
|
|
Track *const pFocusedTrack = trackPanel->GetFocusedTrack();
|
|
|
|
// Look for a label track at or after the focused track
|
|
auto iter = pFocusedTrack
|
|
? tracks->Find(pFocusedTrack)
|
|
: tracks->Any().begin();
|
|
auto lt = * iter.Filter< LabelTrack >();
|
|
|
|
// If none found, start a NEW label track and use it
|
|
if (!lt) {
|
|
lt = static_cast<LabelTrack*>
|
|
(tracks->Add(trackFactory->NewLabelTrack()));
|
|
}
|
|
|
|
// LLL: Commented as it seemed a little forceful to remove users
|
|
// selection when adding the label. This does not happen if
|
|
// you select several tracks and the last one of those is a
|
|
// label track...typing a label will not clear the selections.
|
|
//
|
|
// SelectNone();
|
|
lt->SetSelected(true);
|
|
|
|
int focusTrackNumber;
|
|
if (useDialog) {
|
|
focusTrackNumber = -2;
|
|
}
|
|
else {
|
|
focusTrackNumber = -1;
|
|
if (pFocusedTrack && preserveFocus) {
|
|
// Must remember the track to re-focus after finishing a label edit.
|
|
// do NOT identify it by a pointer, which might dangle! Identify
|
|
// by position.
|
|
focusTrackNumber = pFocusedTrack->GetIndex();
|
|
}
|
|
}
|
|
|
|
int index = lt->AddLabel(region, title, focusTrackNumber);
|
|
|
|
project.PushState(_("Added label"), _("Label"));
|
|
|
|
project.RedrawProject();
|
|
if (!useDialog) {
|
|
trackPanel->EnsureVisible(lt);
|
|
}
|
|
trackPanel->SetFocus();
|
|
|
|
return index;
|
|
}
|
|
|
|
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::OnAddLabel(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto &selectedRegion = project.GetViewInfo().selectedRegion;
|
|
|
|
DoAddLabel(project, selectedRegion);
|
|
}
|
|
|
|
void MenuCommandHandler::OnAddLabelPlaying(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto token = project.GetAudioIOToken();
|
|
|
|
if (token > 0 &&
|
|
gAudioIO->IsStreamActive(token)) {
|
|
double indicator = gAudioIO->GetStreamTime();
|
|
DoAddLabel(project, SelectedRegion(indicator, indicator), true);
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::DoEditLabels
|
|
(AudacityProject &project, LabelTrack *lt, int index)
|
|
{
|
|
auto format = project.GetSelectionFormat(),
|
|
freqFormat = project.GetFrequencySelectionFormatName();
|
|
auto tracks = project.GetTracks();
|
|
auto trackFactory = project.GetTrackFactory();
|
|
auto rate = project.GetRate();
|
|
auto &viewInfo = project.GetViewInfo();
|
|
|
|
LabelDialog dlg(&project, *trackFactory, tracks,
|
|
lt, index,
|
|
viewInfo, rate,
|
|
format, freqFormat);
|
|
|
|
if (dlg.ShowModal() == wxID_OK) {
|
|
project.PushState(_("Edited labels"), _("Label"));
|
|
project.RedrawProject();
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::OnEditLabels(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
DoEditLabels(project);
|
|
}
|
|
|
|
void MenuCommandHandler::OnToggleTypeToCreateLabel(const CommandContext &WXUNUSED(context) )
|
|
{
|
|
bool typeToCreateLabel;
|
|
gPrefs->Read(wxT("/GUI/TypeToCreateLabel"), &typeToCreateLabel, true);
|
|
gPrefs->Write(wxT("/GUI/TypeToCreateLabel"), !typeToCreateLabel);
|
|
gPrefs->Flush();
|
|
MenuManager::ModifyAllProjectToolbarMenus();
|
|
}
|
|
|
|
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::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::OnCollapseAllTracks(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tracks = project.GetTracks();
|
|
|
|
for (auto t : tracks->Any())
|
|
t->SetMinimized(true);
|
|
|
|
project.ModifyState(true);
|
|
project.RedrawProject();
|
|
}
|
|
|
|
void MenuCommandHandler::OnExpandAllTracks(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tracks = project.GetTracks();
|
|
|
|
for (auto t : tracks->Any())
|
|
t->SetMinimized(false);
|
|
|
|
project.ModifyState(true);
|
|
project.RedrawProject();
|
|
}
|
|
|
|
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::OnLockPlayRegion(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto tracks = project.GetTracks();
|
|
auto ruler = project.GetRulerPanel();
|
|
|
|
double start, end;
|
|
project.GetPlayRegion(&start, &end);
|
|
if (start >= tracks->GetEndTime()) {
|
|
AudacityMessageBox(_("Cannot lock region beyond\nend of project."),
|
|
_("Error"));
|
|
}
|
|
else {
|
|
project.SetPlayRegionLocked( true );
|
|
ruler->Refresh(false);
|
|
}
|
|
}
|
|
|
|
void MenuCommandHandler::OnUnlockPlayRegion(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
auto ruler = project.GetRulerPanel();
|
|
|
|
project.SetPlayRegionLocked( false );
|
|
ruler->Refresh(false);
|
|
}
|
|
|
|
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::OnSnapToOff(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
project.SetSnapTo(SNAP_OFF);
|
|
}
|
|
|
|
void MenuCommandHandler::OnSnapToNearest(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
project.SetSnapTo(SNAP_NEAREST);
|
|
}
|
|
|
|
void MenuCommandHandler::OnSnapToPrior(const CommandContext &context)
|
|
{
|
|
auto &project = context.project;
|
|
project.SetSnapTo(SNAP_PRIOR);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
// Handle small cursor and play head movements
|
|
void MenuCommandHandler::SeekLeftOrRight
|
|
(AudacityProject &project, double direction, SelectionOperation operation)
|
|
{
|
|
// PRL: What I found and preserved, strange though it be:
|
|
// During playback: jump depends on preferences and is independent of the zoom
|
|
// and does not vary if the key is held
|
|
// Else: jump depends on the zoom and gets bigger if the key is held
|
|
|
|
if( project.IsAudioActive() )
|
|
{
|
|
if( operation == CURSOR_MOVE )
|
|
SeekWhenAudioActive(mSeekShort * direction);
|
|
else if( operation == SELECTION_EXTEND )
|
|
SeekWhenAudioActive(mSeekLong * direction);
|
|
// Note: no action for CURSOR_CONTRACT
|
|
return;
|
|
}
|
|
|
|
// If the last adjustment was very recent, we are
|
|
// holding the key down and should move faster.
|
|
const wxLongLong curtime = ::wxGetLocalTimeMillis();
|
|
enum { MIN_INTERVAL = 50 };
|
|
const bool fast = (curtime - mLastSelectionAdjustment < MIN_INTERVAL);
|
|
|
|
mLastSelectionAdjustment = curtime;
|
|
|
|
// How much faster should the cursor move if shift is down?
|
|
enum { LARGER_MULTIPLIER = 4 };
|
|
const double seekStep = (fast ? LARGER_MULTIPLIER : 1.0) * direction;
|
|
|
|
SeekWhenAudioInactive( project, seekStep, TIME_UNIT_PIXELS, operation);
|
|
}
|
|
|
|
void MenuCommandHandler::SeekWhenAudioActive(double seekStep)
|
|
{
|
|
#ifdef EXPERIMENTAL_IMPROVED_SEEKING
|
|
if (gAudioIO->GetLastPlaybackTime() < mLastSelectionAdjustment) {
|
|
// Allow time for the last seek to output a buffer before
|
|
// discarding samples again
|
|
// Do not advance mLastSelectionAdjustment
|
|
return;
|
|
}
|
|
#endif
|
|
mLastSelectionAdjustment = ::wxGetLocalTimeMillis();
|
|
|
|
gAudioIO->SeekStream(seekStep);
|
|
}
|
|
|
|
|
|
void MenuCommandHandler::DoBoundaryMove(AudacityProject &project, int step)
|
|
{
|
|
auto &viewInfo = project.GetViewInfo();
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto tracks = project.GetTracks();
|
|
|
|
// step is negative, then is moving left. step positive, moving right.
|
|
// Move the left/right selection boundary, to expand the selection
|
|
|
|
// If the last adjustment was very recent, we are
|
|
// holding the key down and should move faster.
|
|
wxLongLong curtime = ::wxGetLocalTimeMillis();
|
|
int pixels = step;
|
|
if( curtime - mLastSelectionAdjustment < 50 )
|
|
{
|
|
pixels *= 4;
|
|
}
|
|
mLastSelectionAdjustment = curtime;
|
|
|
|
// we used to have a parameter boundaryContract to say if expanding or contracting.
|
|
// it is no longer needed.
|
|
bool bMoveT0 = (step < 0 );// ^ boundaryContract ;
|
|
|
|
if( project.IsAudioActive() )
|
|
{
|
|
double indicator = gAudioIO->GetStreamTime();
|
|
if( bMoveT0 )
|
|
viewInfo.selectedRegion.setT0(indicator, false);
|
|
else
|
|
viewInfo.selectedRegion.setT1(indicator);
|
|
|
|
project.ModifyState(false);
|
|
trackPanel->Refresh(false);
|
|
return;
|
|
}
|
|
|
|
const double t0 = viewInfo.selectedRegion.t0();
|
|
const double t1 = viewInfo.selectedRegion.t1();
|
|
const double end = std::max(
|
|
tracks->GetEndTime(),
|
|
trackPanel->GetScreenEndTime());
|
|
|
|
double newT = viewInfo.OffsetTimeByPixels( bMoveT0 ? t0 : t1, pixels);
|
|
// constrain to be in the track/screen limits.
|
|
newT = std::max( 0.0, newT );
|
|
newT = std::min( newT, end);
|
|
// optionally constrain to be a contraction, i.e. so t0/t1 do not cross over
|
|
//if( boundaryContract )
|
|
// newT = bMoveT0 ? std::min( t1, newT ) : std::max( t0, newT );
|
|
|
|
// Actually move
|
|
if( bMoveT0 )
|
|
viewInfo.selectedRegion.setT0( newT );
|
|
else
|
|
viewInfo.selectedRegion.setT1( newT );
|
|
|
|
// Ensure it is visible, and refresh.
|
|
trackPanel->ScrollIntoView(newT);
|
|
trackPanel->Refresh(false);
|
|
|
|
project.ModifyState(false);
|
|
}
|
|
|
|
void MenuCommandHandler::SeekWhenAudioInactive
|
|
(AudacityProject &project, double seekStep, TimeUnit timeUnit,
|
|
SelectionOperation operation)
|
|
{
|
|
auto &viewInfo = project.GetViewInfo();
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto tracks = project.GetTracks();
|
|
|
|
if( operation == CURSOR_MOVE )
|
|
{
|
|
MoveWhenAudioInactive( project, seekStep, timeUnit);
|
|
return;
|
|
}
|
|
|
|
int snapToTime = project.GetSnapTo();
|
|
const double t0 = viewInfo.selectedRegion.t0();
|
|
const double t1 = viewInfo.selectedRegion.t1();
|
|
const double end = std::max(
|
|
tracks->GetEndTime(),
|
|
trackPanel->GetScreenEndTime());
|
|
|
|
// Is it t0 or t1 moving?
|
|
bool bMoveT0 = (operation == SELECTION_CONTRACT && seekStep > 0) ||
|
|
(operation == SELECTION_EXTEND && seekStep < 0);
|
|
// newT is where we want to move to
|
|
double newT = OffsetTime( project,
|
|
bMoveT0 ? t0 : t1, seekStep, timeUnit, snapToTime);
|
|
// constrain to be in the track/screen limits.
|
|
newT = std::max( 0.0, newT );
|
|
newT = std::min( newT, end);
|
|
// optionally constrain to be a contraction, i.e. so t0/t1 do not cross over
|
|
if( operation == SELECTION_CONTRACT )
|
|
newT = bMoveT0 ? std::min( t1, newT ) : std::max( t0, newT );
|
|
|
|
// Actually move
|
|
if( bMoveT0 )
|
|
viewInfo.selectedRegion.setT0( newT );
|
|
else
|
|
viewInfo.selectedRegion.setT1( newT );
|
|
|
|
// Ensure it is visible, and refresh.
|
|
trackPanel->ScrollIntoView(newT);
|
|
trackPanel->Refresh(false);
|
|
}
|
|
|
|
// Moving a cursor, and collapsed selection.
|
|
void MenuCommandHandler::MoveWhenAudioInactive
|
|
(AudacityProject &project, double seekStep, TimeUnit timeUnit)
|
|
{
|
|
auto &viewInfo = project.GetViewInfo();
|
|
auto trackPanel = project.GetTrackPanel();
|
|
auto tracks = project.GetTracks();
|
|
auto ruler = project.GetRulerPanel();
|
|
|
|
// If TIME_UNIT_SECONDS, snap-to will be off.
|
|
int snapToTime = project.GetSnapTo();
|
|
const double t0 = viewInfo.selectedRegion.t0();
|
|
const double end = std::max(
|
|
tracks->GetEndTime(),
|
|
trackPanel->GetScreenEndTime());
|
|
|
|
// Move the cursor
|
|
// Already in cursor mode?
|
|
if( viewInfo.selectedRegion.isPoint() )
|
|
{
|
|
double newT = OffsetTime(project,
|
|
t0, seekStep, timeUnit, snapToTime);
|
|
// constrain.
|
|
newT = std::max(0.0, newT);
|
|
newT = std::min(newT, end);
|
|
// Move
|
|
viewInfo.selectedRegion.setT0(
|
|
newT,
|
|
false); // do not swap selection boundaries
|
|
viewInfo.selectedRegion.collapseToT0();
|
|
|
|
// Move the visual cursor, avoiding an unnecessary complete redraw
|
|
trackPanel->DrawOverlays(false);
|
|
ruler->DrawOverlays(false);
|
|
|
|
// This updates the selection shown on the selection bar, and the play region
|
|
project.TP_DisplaySelection();
|
|
} else
|
|
{
|
|
// Transition to cursor mode.
|
|
if( seekStep < 0 )
|
|
viewInfo.selectedRegion.collapseToT0();
|
|
else
|
|
viewInfo.selectedRegion.collapseToT1();
|
|
trackPanel->Refresh(false);
|
|
}
|
|
|
|
// Make sure NEW position is in view
|
|
trackPanel->ScrollIntoView(viewInfo.selectedRegion.t1());
|
|
return;
|
|
}
|
|
|
|
double MenuCommandHandler::OffsetTime
|
|
(AudacityProject &project,
|
|
double t, double offset, TimeUnit timeUnit, int snapToTime)
|
|
{
|
|
auto &viewInfo = project.GetViewInfo();
|
|
|
|
if (timeUnit == TIME_UNIT_SECONDS)
|
|
return t + offset; // snapping is currently ignored for non-pixel moves
|
|
|
|
if (snapToTime == SNAP_OFF)
|
|
return viewInfo.OffsetTimeByPixels(t, (int)offset);
|
|
|
|
return GridMove(project, t, (int)offset);
|
|
}
|
|
|
|
// Handles moving a selection edge with the keyboard in snap-to-time mode;
|
|
// returns the moved value.
|
|
// Will move at least minPix pixels -- set minPix positive to move forward,
|
|
// negative to move backward.
|
|
double MenuCommandHandler::GridMove
|
|
(AudacityProject &project, double t, int minPix)
|
|
{
|
|
auto rate = project.GetRate();
|
|
auto &viewInfo = project.GetViewInfo();
|
|
auto format = project.GetSelectionFormat();
|
|
|
|
NumericConverter nc(NumericConverter::TIME, format, t, rate);
|
|
|
|
// Try incrementing/decrementing the value; if we've moved far enough we're
|
|
// done
|
|
double result;
|
|
minPix >= 0 ? nc.Increment() : nc.Decrement();
|
|
result = nc.GetValue();
|
|
if (std::abs(viewInfo.TimeToPosition(result) - viewInfo.TimeToPosition(t))
|
|
>= abs(minPix))
|
|
return result;
|
|
|
|
// Otherwise, move minPix pixels, then snap to the time.
|
|
result = viewInfo.OffsetTimeByPixels(t, minPix);
|
|
nc.SetValue(result);
|
|
result = nc.GetValue();
|
|
return result;
|
|
}
|
|
|
|
|
|
// Move the cursor forward or backward, while paused or while playing.
|
|
void MenuCommandHandler::DoCursorMove(
|
|
AudacityProject &project, double seekStep)
|
|
{
|
|
if (project.IsAudioActive()) {
|
|
SeekWhenAudioActive(seekStep);
|
|
}
|
|
else
|
|
{
|
|
mLastSelectionAdjustment = ::wxGetLocalTimeMillis();
|
|
MoveWhenAudioInactive(project, seekStep, TIME_UNIT_SECONDS);
|
|
}
|
|
|
|
project.ModifyState(false);
|
|
}
|