mirror of
https://github.com/cookiengineer/audacity
synced 2025-08-02 08:59:28 +02:00
... Problem with static initialization order of ReservedCommandFlags, caused wrong enablement of menu items (at least on Mac), such as Plot Spectrum or Contrast enabled when there was no selection
2099 lines
55 KiB
C++
2099 lines
55 KiB
C++
/**********************************************************************
|
|
|
|
Audacity: A Digital Audio Editor
|
|
|
|
EffectUI.cpp
|
|
|
|
Leland Lucius
|
|
|
|
Audacity(R) is copyright (c) 1999-2008 Audacity Team.
|
|
License: GPL v2. See License.txt.
|
|
|
|
**********************************************************************/
|
|
|
|
#include "../Audacity.h"
|
|
#include "EffectUI.h"
|
|
|
|
#include "../Experimental.h"
|
|
|
|
#include "Effect.h"
|
|
#include "EffectManager.h"
|
|
#include "../ProjectHistory.h"
|
|
#include "../ProjectWindowBase.h"
|
|
#include "../TrackPanelAx.h"
|
|
#include "RealtimeEffectManager.h"
|
|
|
|
#if defined(EXPERIMENTAL_EFFECTS_RACK)
|
|
|
|
#include "../UndoManager.h"
|
|
|
|
#include <wx/defs.h>
|
|
#include <wx/bmpbuttn.h>
|
|
#include <wx/button.h>
|
|
#include <wx/dcmemory.h>
|
|
#include <wx/frame.h>
|
|
#include <wx/image.h>
|
|
#include <wx/imaglist.h>
|
|
#include <wx/settings.h>
|
|
#include <wx/sizer.h>
|
|
#include <wx/statline.h>
|
|
#include <wx/stattext.h>
|
|
#include <wx/timer.h>
|
|
#include <wx/tglbtn.h>
|
|
|
|
#include "../commands/CommandContext.h"
|
|
#include "../Prefs.h"
|
|
#include "../Project.h"
|
|
#include "../widgets/wxPanelWrapper.h"
|
|
|
|
#include "../../images/EffectRack/EffectRack.h"
|
|
|
|
#define COL_POWER 0
|
|
#define COL_EDITOR 1
|
|
#define COL_UP 2
|
|
#define COL_DOWN 3
|
|
#define COL_FAV 4
|
|
#define COL_REMOVE 5
|
|
#define COL_NAME 6
|
|
#define NUMCOLS 7
|
|
|
|
#define ID_BASE 20000
|
|
#define ID_RANGE 100
|
|
#define ID_POWER (ID_BASE + (COL_POWER * ID_RANGE))
|
|
#define ID_EDITOR (ID_BASE + (COL_EDITOR * ID_RANGE))
|
|
#define ID_UP (ID_BASE + (COL_UP * ID_RANGE))
|
|
#define ID_DOWN (ID_BASE + (COL_DOWN * ID_RANGE))
|
|
#define ID_FAV (ID_BASE + (COL_FAV * ID_RANGE))
|
|
#define ID_REMOVE (ID_BASE + (COL_REMOVE * ID_RANGE))
|
|
#define ID_NAME (ID_BASE + (COL_NAME * ID_RANGE))
|
|
|
|
BEGIN_EVENT_TABLE(EffectRack, wxFrame)
|
|
EVT_CLOSE(EffectRack::OnClose)
|
|
EVT_TIMER(wxID_ANY, EffectRack::OnTimer)
|
|
|
|
EVT_BUTTON(wxID_APPLY, EffectRack::OnApply)
|
|
EVT_TOGGLEBUTTON(wxID_CLEAR, EffectRack::OnBypass)
|
|
|
|
EVT_COMMAND_RANGE(ID_REMOVE, ID_REMOVE + 99, wxEVT_COMMAND_BUTTON_CLICKED, EffectRack::OnRemove)
|
|
EVT_COMMAND_RANGE(ID_POWER, ID_POWER + 99, wxEVT_COMMAND_BUTTON_CLICKED, EffectRack::OnPower)
|
|
EVT_COMMAND_RANGE(ID_EDITOR, ID_EDITOR + 99, wxEVT_COMMAND_BUTTON_CLICKED, EffectRack::OnEditor)
|
|
EVT_COMMAND_RANGE(ID_UP, ID_UP + 99, wxEVT_COMMAND_BUTTON_CLICKED, EffectRack::OnUp)
|
|
EVT_COMMAND_RANGE(ID_DOWN, ID_DOWN + 99, wxEVT_COMMAND_BUTTON_CLICKED, EffectRack::OnDown)
|
|
EVT_COMMAND_RANGE(ID_FAV, ID_FAV + 99, wxEVT_COMMAND_BUTTON_CLICKED, EffectRack::OnFav)
|
|
END_EVENT_TABLE()
|
|
|
|
EffectRack::EffectRack( AudacityProject &project )
|
|
: wxFrame( &GetProjectFrame( project ),
|
|
wxID_ANY,
|
|
_("Effects Rack"),
|
|
wxDefaultPosition,
|
|
wxDefaultSize,
|
|
wxSYSTEM_MENU |
|
|
wxCLOSE_BOX |
|
|
wxCAPTION |
|
|
wxFRAME_NO_TASKBAR |
|
|
wxFRAME_FLOAT_ON_PARENT)
|
|
, mProject{ project }
|
|
{
|
|
mBypassing = false;
|
|
mNumEffects = 0;
|
|
mLastLatency = 0;
|
|
mTimer.SetOwner(this);
|
|
|
|
mPowerPushed = CreateBitmap(power_on_16x16_xpm, false, false);
|
|
mPowerRaised = CreateBitmap(power_off_16x16_xpm, true, false);
|
|
mSettingsPushed = CreateBitmap(settings_up_16x16_xpm, false, true);
|
|
mSettingsRaised = CreateBitmap(settings_down_16x16_xpm, true, true);
|
|
mUpDisabled = CreateBitmap(up_9x16_xpm, true, true);
|
|
mUpPushed = CreateBitmap(up_9x16_xpm, false, true);
|
|
mUpRaised = CreateBitmap(up_9x16_xpm, true, true);
|
|
mDownDisabled = CreateBitmap(down_9x16_xpm, true, true);
|
|
mDownPushed = CreateBitmap(down_9x16_xpm, false, true);
|
|
mDownRaised = CreateBitmap(down_9x16_xpm, true, true);
|
|
mFavPushed = CreateBitmap(fav_down_16x16_xpm, false, false);
|
|
mFavRaised = CreateBitmap(fav_up_16x16_xpm, true, false);
|
|
mRemovePushed = CreateBitmap(remove_16x16_xpm, false, true);
|
|
mRemoveRaised = CreateBitmap(remove_16x16_xpm, true, true);
|
|
|
|
{
|
|
auto bs = std::make_unique<wxBoxSizer>(wxVERTICAL);
|
|
mPanel = safenew wxPanelWrapper(this, wxID_ANY);
|
|
bs->Add(mPanel, 1, wxEXPAND);
|
|
SetSizer(bs.release());
|
|
}
|
|
|
|
{
|
|
auto bs = std::make_unique<wxBoxSizer>(wxVERTICAL);
|
|
{
|
|
auto hs = std::make_unique<wxBoxSizer>(wxHORIZONTAL);
|
|
wxASSERT(mPanel); // To justify safenew
|
|
hs->Add(safenew wxButton(mPanel, wxID_APPLY, _("&Apply")), 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
|
|
hs->AddStretchSpacer();
|
|
mLatency = safenew wxStaticText(mPanel, wxID_ANY, _("Latency: 0"));
|
|
hs->Add(mLatency, 0, wxALIGN_CENTER);
|
|
hs->AddStretchSpacer();
|
|
hs->Add(safenew wxToggleButton(mPanel, wxID_CLEAR, _("&Bypass")), 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL);
|
|
bs->Add(hs.release(), 0, wxEXPAND);
|
|
}
|
|
bs->Add(safenew wxStaticLine(mPanel, wxID_ANY), 0, wxEXPAND);
|
|
|
|
{
|
|
auto uMainSizer = std::make_unique<wxFlexGridSizer>(7);
|
|
uMainSizer->AddGrowableCol(6);
|
|
uMainSizer->SetHGap(0);
|
|
uMainSizer->SetVGap(0);
|
|
bs->Add((mMainSizer = uMainSizer.release()), 1, wxEXPAND);
|
|
}
|
|
|
|
mPanel->SetSizer(bs.release());
|
|
}
|
|
|
|
wxString oldPath = gPrefs->GetPath();
|
|
gPrefs->SetPath(wxT("/EffectsRack"));
|
|
size_t cnt = gPrefs->GetNumberOfEntries();
|
|
gPrefs->SetPath(oldPath);
|
|
|
|
EffectManager & em = EffectManager::Get();
|
|
for (size_t i = 0; i < cnt; i++)
|
|
{
|
|
wxString slot;
|
|
gPrefs->Read(wxString::Format(wxT("/EffectsRack/Slot%02d"), i), &slot);
|
|
|
|
Effect *effect = em.GetEffect(slot.AfterFirst(wxT(',')));
|
|
if (effect)
|
|
{
|
|
Add(effect, slot.BeforeFirst(wxT(',')) == wxT("1"), true);
|
|
}
|
|
}
|
|
|
|
Fit();
|
|
}
|
|
|
|
EffectRack::~EffectRack()
|
|
{
|
|
gPrefs->DeleteGroup(wxT("/EffectsRack"));
|
|
|
|
for (size_t i = 0, cnt = mEffects.size(); i < cnt; i++)
|
|
{
|
|
if (mFavState[i])
|
|
{
|
|
Effect *effect = mEffects[i];
|
|
gPrefs->Write(wxString::Format(wxT("/EffectsRack/Slot%02d"), i),
|
|
wxString::Format(wxT("%d,%s"),
|
|
mPowerState[i],
|
|
effect->GetID()));
|
|
}
|
|
}
|
|
}
|
|
|
|
void EffectRack::Add(Effect *effect, bool active, bool favorite)
|
|
{
|
|
if (mEffects.end() != std::find(mEffects.begin(), mEffects.end(), effect))
|
|
{
|
|
return;
|
|
}
|
|
|
|
wxBitmapButton *bb;
|
|
|
|
wxASSERT(mPanel); // To justify safenew
|
|
bb = safenew wxBitmapButton(mPanel, ID_POWER + mNumEffects, mPowerRaised);
|
|
bb->SetBitmapSelected(mPowerRaised);
|
|
bb->SetName(_("Active State"));
|
|
bb->SetToolTip(_("Set effect active state"));
|
|
mPowerState.push_back(active);
|
|
if (active)
|
|
{
|
|
bb->SetBitmapLabel(mPowerPushed);
|
|
bb->SetBitmapSelected(mPowerPushed);
|
|
}
|
|
else
|
|
{
|
|
bb->SetBitmapLabel(mPowerRaised);
|
|
bb->SetBitmapSelected(mPowerRaised);
|
|
}
|
|
mMainSizer->Add(bb, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
|
|
|
|
bb = safenew wxBitmapButton(mPanel, ID_EDITOR + mNumEffects, mSettingsRaised);
|
|
bb->SetBitmapSelected(mSettingsPushed);
|
|
bb->SetName(_("Show/Hide Editor"));
|
|
bb->SetToolTip(_("Open/close effect editor"));
|
|
mMainSizer->Add(bb, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
|
|
|
|
bb = safenew wxBitmapButton(mPanel, ID_UP + mNumEffects, mUpRaised);
|
|
bb->SetBitmapSelected(mUpPushed);
|
|
bb->SetBitmapDisabled(mUpDisabled);
|
|
bb->SetName(_("Move Up"));
|
|
bb->SetToolTip(_("Move effect up in the rack"));
|
|
mMainSizer->Add(bb, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
|
|
|
|
bb = safenew wxBitmapButton(mPanel, ID_DOWN + mNumEffects, mDownRaised);
|
|
bb->SetBitmapSelected(mDownPushed);
|
|
bb->SetBitmapDisabled(mDownDisabled);
|
|
bb->SetName(_("Move Down"));
|
|
bb->SetToolTip(_("Move effect down in the rack"));
|
|
mMainSizer->Add(bb, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
|
|
|
|
bb = safenew wxBitmapButton(mPanel, ID_FAV + mNumEffects, mFavRaised);
|
|
bb->SetBitmapSelected(mFavPushed);
|
|
bb->SetName(_("Favorite"));
|
|
bb->SetToolTip(_("Mark effect as a favorite"));
|
|
mFavState.push_back(favorite);
|
|
if (favorite)
|
|
{
|
|
bb->SetBitmapLabel(mFavPushed);
|
|
bb->SetBitmapSelected(mFavPushed);
|
|
}
|
|
else
|
|
{
|
|
bb->SetBitmapLabel(mFavRaised);
|
|
bb->SetBitmapSelected(mFavRaised);
|
|
}
|
|
mMainSizer->Add(bb, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
|
|
|
|
bb = safenew wxBitmapButton(mPanel, ID_REMOVE + mNumEffects, mRemoveRaised);
|
|
bb->SetBitmapSelected(mRemovePushed);
|
|
bb->SetName(_("Remove"));
|
|
bb->SetToolTip(_("Remove effect from the rack"));
|
|
mMainSizer->Add(bb, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
|
|
|
|
wxStaticText *text = safenew wxStaticText(mPanel, ID_NAME + mNumEffects,
|
|
effect->GetName().Translation() );
|
|
text->SetToolTip(_("Name of the effect"));
|
|
mMainSizer->Add(text, 0, wxEXPAND | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
|
|
|
mMainSizer->Layout();
|
|
SetSize(GetMinSize());
|
|
Fit();
|
|
Update();
|
|
|
|
mEffects.push_back(effect);
|
|
mNumEffects++;
|
|
|
|
if (!mTimer.IsRunning())
|
|
{
|
|
mTimer.Start(1000);
|
|
}
|
|
|
|
if (active)
|
|
{
|
|
UpdateActive();
|
|
}
|
|
}
|
|
|
|
void EffectRack::OnClose(wxCloseEvent & evt)
|
|
{
|
|
Show(false);
|
|
evt.Veto();
|
|
}
|
|
|
|
void EffectRack::OnTimer(wxTimerEvent & WXUNUSED(evt))
|
|
{
|
|
int latency = RealtimeEffectManager::Get().GetRealtimeLatency();
|
|
if (latency != mLastLatency)
|
|
{
|
|
mLatency->SetLabel(wxString::Format(_("Latency: %4d"), latency));
|
|
mLatency->Refresh();
|
|
mLastLatency = latency;
|
|
}
|
|
}
|
|
|
|
void EffectRack::OnApply(wxCommandEvent & WXUNUSED(evt))
|
|
{
|
|
AudacityProject *project = &mProject;
|
|
|
|
bool success = false;
|
|
auto state = UndoManager::Get( *project ).GetCurrentState();
|
|
auto cleanup = finally( [&] {
|
|
if(!success)
|
|
ProjectHistory::Get( *project ).SetStateTo( state );
|
|
} );
|
|
|
|
for (size_t i = 0, cnt = mEffects.size(); i < cnt; i++)
|
|
{
|
|
if (mPowerState[i])
|
|
{
|
|
if (!EffectUI::DoEffect(mEffects[i]->GetID(),
|
|
*project,
|
|
EffectManager::kConfigured))
|
|
// If any effect fails (or throws), then stop.
|
|
return;
|
|
}
|
|
}
|
|
|
|
success = true;
|
|
|
|
// Only after all succeed, do the following.
|
|
for (size_t i = 0, cnt = mEffects.size(); i < cnt; i++)
|
|
{
|
|
if (mPowerState[i])
|
|
{
|
|
mPowerState[i] = false;
|
|
|
|
wxBitmapButton *btn =
|
|
static_cast<wxBitmapButton *>(FindWindowById(ID_POWER + i));
|
|
btn->SetBitmapLabel(mPowerRaised);
|
|
btn->SetBitmapSelected(mPowerRaised);
|
|
}
|
|
}
|
|
|
|
UpdateActive();
|
|
}
|
|
|
|
void EffectRack::OnBypass(wxCommandEvent & evt)
|
|
{
|
|
mBypassing = evt.GetInt() != 0;
|
|
UpdateActive();
|
|
}
|
|
|
|
void EffectRack::OnPower(wxCommandEvent & evt)
|
|
{
|
|
wxBitmapButton *btn = static_cast<wxBitmapButton *>(evt.GetEventObject());
|
|
|
|
int index = GetEffectIndex(btn);
|
|
mPowerState[index] = !mPowerState[index];
|
|
if (mPowerState[index])
|
|
{
|
|
btn->SetBitmapLabel(mPowerPushed);
|
|
btn->SetBitmapSelected(mPowerPushed);
|
|
}
|
|
else
|
|
{
|
|
btn->SetBitmapLabel(mPowerRaised);
|
|
btn->SetBitmapSelected(mPowerRaised);
|
|
}
|
|
|
|
UpdateActive();
|
|
}
|
|
|
|
void EffectRack::OnEditor(wxCommandEvent & evt)
|
|
{
|
|
wxBitmapButton *btn = static_cast<wxBitmapButton *>(evt.GetEventObject());
|
|
|
|
evt.Skip();
|
|
|
|
int index = GetEffectIndex(btn);
|
|
if (index < 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
auto pEffect = mEffects[index];
|
|
pEffect->ShowInterface( *GetParent(), EffectUI::DialogFactory,
|
|
pEffect->IsBatchProcessing() );
|
|
}
|
|
|
|
void EffectRack::OnUp(wxCommandEvent & evt)
|
|
{
|
|
wxBitmapButton *btn = static_cast<wxBitmapButton *>(evt.GetEventObject());
|
|
|
|
evt.Skip();
|
|
|
|
int index = GetEffectIndex(btn);
|
|
if (index <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
MoveRowUp(index);
|
|
}
|
|
|
|
void EffectRack::OnDown(wxCommandEvent & evt)
|
|
{
|
|
wxBitmapButton *btn = static_cast<wxBitmapButton *>(evt.GetEventObject());
|
|
|
|
evt.Skip();
|
|
|
|
int index = GetEffectIndex(btn);
|
|
if (index < 0 || index == (mMainSizer->GetChildren().GetCount() / NUMCOLS) - 1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
MoveRowUp(index + 1);
|
|
}
|
|
|
|
void EffectRack::OnFav(wxCommandEvent & evt)
|
|
{
|
|
wxBitmapButton *btn = static_cast<wxBitmapButton *>(evt.GetEventObject());
|
|
|
|
int index = GetEffectIndex(btn);
|
|
mFavState[index] = !mFavState[index];
|
|
if (mFavState[index])
|
|
{
|
|
btn->SetBitmapLabel(mFavPushed);
|
|
btn->SetBitmapSelected(mFavPushed);
|
|
}
|
|
else
|
|
{
|
|
btn->SetBitmapLabel(mFavRaised);
|
|
btn->SetBitmapSelected(mFavRaised);
|
|
}
|
|
}
|
|
|
|
void EffectRack::OnRemove(wxCommandEvent & evt)
|
|
{
|
|
wxBitmapButton *btn = static_cast<wxBitmapButton *>(evt.GetEventObject());
|
|
|
|
evt.Skip();
|
|
|
|
int index = GetEffectIndex(btn);
|
|
if (index < 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
mEffects.erase(mEffects.begin() + index);
|
|
mPowerState.erase(mPowerState.begin() + index);
|
|
mFavState.erase(mFavState.begin() + index);
|
|
|
|
if (mEffects.size() == 0)
|
|
{
|
|
if (mTimer.IsRunning())
|
|
{
|
|
mTimer.Stop();
|
|
}
|
|
}
|
|
|
|
index *= NUMCOLS;
|
|
|
|
for (int i = 0; i < NUMCOLS; i++)
|
|
{
|
|
std::unique_ptr<wxWindow> w {mMainSizer->GetItem(index)->GetWindow()};
|
|
mMainSizer->Detach(index);
|
|
}
|
|
|
|
mMainSizer->Layout();
|
|
Fit();
|
|
|
|
UpdateActive();
|
|
}
|
|
|
|
wxBitmap EffectRack::CreateBitmap(const char *const xpm[], bool up, bool pusher)
|
|
{
|
|
wxMemoryDC dc;
|
|
wxBitmap pic(xpm);
|
|
|
|
wxBitmap mod(pic.GetWidth() + 6, pic.GetHeight() + 6);
|
|
dc.SelectObject(mod);
|
|
#if defined( __WXGTK__ )
|
|
wxColour newColour = wxSystemSettings::GetColour( wxSYS_COLOUR_BACKGROUND );
|
|
#else
|
|
wxColour newColour = wxSystemSettings::GetColour( wxSYS_COLOUR_3DFACE );
|
|
#endif
|
|
dc.SetBackground(wxBrush(newColour));
|
|
dc.Clear();
|
|
|
|
int offset = 3;
|
|
if (pusher)
|
|
{
|
|
if (!up)
|
|
{
|
|
offset += 1;
|
|
}
|
|
}
|
|
dc.DrawBitmap(pic, offset, offset, true);
|
|
|
|
dc.SelectObject(wxNullBitmap);
|
|
|
|
return mod;
|
|
}
|
|
|
|
int EffectRack::GetEffectIndex(wxWindow *win)
|
|
{
|
|
int col = (win->GetId() - ID_BASE) / ID_RANGE;
|
|
int row;
|
|
int cnt = mMainSizer->GetChildren().GetCount() / NUMCOLS;
|
|
for (row = 0; row < cnt; row++)
|
|
{
|
|
wxSizerItem *si = mMainSizer->GetItem((row * NUMCOLS) + col);
|
|
if (si->GetWindow() == win)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (row == cnt)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
return row;
|
|
}
|
|
|
|
void EffectRack::MoveRowUp(int row)
|
|
{
|
|
Effect *effect = mEffects[row];
|
|
mEffects.erase(mEffects.begin() + row);
|
|
mEffects.insert(mEffects.begin() + row - 1, effect);
|
|
|
|
int state = mPowerState[row];
|
|
mPowerState.erase(mPowerState.begin() + row);
|
|
mPowerState.insert(mPowerState.begin() + row - 1, state);
|
|
|
|
state = mFavState[row];
|
|
mFavState.erase(mFavState.begin() + row);
|
|
mFavState.insert(mFavState.begin() + row - 1, state);
|
|
|
|
row *= NUMCOLS;
|
|
|
|
for (int i = 0; i < NUMCOLS; i++)
|
|
{
|
|
wxSizerItem *si = mMainSizer->GetItem(row + NUMCOLS - 1);
|
|
wxWindow *w = si->GetWindow();
|
|
int flags = si->GetFlag();
|
|
int border = si->GetBorder();
|
|
int prop = si->GetProportion();
|
|
mMainSizer->Detach(row + NUMCOLS - 1);
|
|
mMainSizer->Insert(row - NUMCOLS, w, prop, flags, border);
|
|
}
|
|
|
|
mMainSizer->Layout();
|
|
Refresh();
|
|
|
|
UpdateActive();
|
|
}
|
|
|
|
void EffectRack::UpdateActive()
|
|
{
|
|
mActive.clear();
|
|
|
|
if (!mBypassing)
|
|
{
|
|
for (size_t i = 0, cnt = mEffects.size(); i < cnt; i++)
|
|
{
|
|
if (mPowerState[i])
|
|
{
|
|
mActive.push_back(mEffects[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
RealtimeEffectManager::Get().RealtimeSetEffects(
|
|
{ mActive.begin(), mActive.end() }
|
|
);
|
|
}
|
|
|
|
namespace
|
|
{
|
|
AudacityProject::AttachedWindows::RegisteredFactory sKey{
|
|
[]( AudacityProject &parent ) -> wxWeakRef< wxWindow > {
|
|
auto result = safenew EffectRack( parent );
|
|
result->CenterOnParent();
|
|
return result;
|
|
}
|
|
};
|
|
}
|
|
|
|
EffectRack &EffectRack::Get( AudacityProject &project )
|
|
{
|
|
return project.AttachedWindows::Get< EffectRack >( sKey );
|
|
}
|
|
|
|
#endif
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// EffectPanel
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
class EffectPanel final : public wxPanelWrapper
|
|
{
|
|
public:
|
|
EffectPanel(wxWindow *parent)
|
|
: wxPanelWrapper(parent)
|
|
{
|
|
// This fools NVDA into not saying "Panel" when the dialog gets focus
|
|
SetName(TranslatableString::Inaudible);
|
|
SetLabel(TranslatableString::Inaudible);
|
|
|
|
mAcceptsFocus = true;
|
|
}
|
|
|
|
virtual ~EffectPanel()
|
|
{
|
|
}
|
|
|
|
// ============================================================================
|
|
// wxWindow implementation
|
|
// ============================================================================
|
|
|
|
bool AcceptsFocus() const override
|
|
{
|
|
return mAcceptsFocus;
|
|
}
|
|
|
|
// So that wxPanel is not included in Tab traversal, when required - see wxWidgets bug 15581
|
|
bool AcceptsFocusFromKeyboard() const override
|
|
{
|
|
return mAcceptsFocus;
|
|
}
|
|
|
|
// ============================================================================
|
|
// EffectPanel implementation
|
|
// ============================================================================
|
|
void SetAccept(bool accept)
|
|
{
|
|
mAcceptsFocus = accept;
|
|
}
|
|
|
|
private:
|
|
bool mAcceptsFocus;
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// EffectUIHost
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "../../images/Effect.h"
|
|
#include "../AudioIO.h"
|
|
#include "../CommonCommandFlags.h"
|
|
#include "../Menus.h"
|
|
#include "../Project.h"
|
|
#include "../ProjectAudioManager.h"
|
|
#include "../ShuttleGui.h"
|
|
#include "../ViewInfo.h"
|
|
#include "../commands/AudacityCommand.h"
|
|
#include "../commands/CommandContext.h"
|
|
#include "../widgets/AudacityMessageBox.h"
|
|
#include "../widgets/HelpSystem.h"
|
|
|
|
#include <wx/bmpbuttn.h>
|
|
#include <wx/checkbox.h>
|
|
#include <wx/dcclient.h>
|
|
#include <wx/dcmemory.h>
|
|
#include <wx/menu.h>
|
|
#include <wx/settings.h>
|
|
#include <wx/sizer.h>
|
|
#include <wx/textctrl.h>
|
|
|
|
#if defined(__WXMAC__)
|
|
#include <Cocoa/Cocoa.h>
|
|
#endif
|
|
|
|
static const int kDummyID = 20000;
|
|
static const int kSaveAsID = 20001;
|
|
static const int kImportID = 20002;
|
|
static const int kExportID = 20003;
|
|
static const int kDefaultsID = 20004;
|
|
static const int kOptionsID = 20005;
|
|
static const int kUserPresetsDummyID = 20006;
|
|
static const int kDeletePresetDummyID = 20007;
|
|
static const int kMenuID = 20100;
|
|
static const int kEnableID = 20101;
|
|
static const int kPlayID = 20102;
|
|
static const int kRewindID = 20103;
|
|
static const int kFFwdID = 20104;
|
|
static const int kPlaybackID = 20105;
|
|
static const int kCaptureID = 20106;
|
|
static const int kUserPresetsID = 21000;
|
|
static const int kDeletePresetID = 22000;
|
|
static const int kFactoryPresetsID = 23000;
|
|
|
|
BEGIN_EVENT_TABLE(EffectUIHost, wxDialogWrapper)
|
|
EVT_INIT_DIALOG(EffectUIHost::OnInitDialog)
|
|
EVT_ERASE_BACKGROUND(EffectUIHost::OnErase)
|
|
EVT_PAINT(EffectUIHost::OnPaint)
|
|
EVT_CLOSE(EffectUIHost::OnClose)
|
|
EVT_BUTTON(wxID_APPLY, EffectUIHost::OnApply)
|
|
EVT_BUTTON(wxID_CANCEL, EffectUIHost::OnCancel)
|
|
EVT_BUTTON(wxID_HELP, EffectUIHost::OnHelp)
|
|
EVT_BUTTON(eDebugID, EffectUIHost::OnDebug)
|
|
EVT_BUTTON(kMenuID, EffectUIHost::OnMenu)
|
|
EVT_CHECKBOX(kEnableID, EffectUIHost::OnEnable)
|
|
EVT_BUTTON(kPlayID, EffectUIHost::OnPlay)
|
|
EVT_BUTTON(kRewindID, EffectUIHost::OnRewind)
|
|
EVT_BUTTON(kFFwdID, EffectUIHost::OnFFwd)
|
|
EVT_MENU(kSaveAsID, EffectUIHost::OnSaveAs)
|
|
EVT_MENU(kImportID, EffectUIHost::OnImport)
|
|
EVT_MENU(kExportID, EffectUIHost::OnExport)
|
|
EVT_MENU(kOptionsID, EffectUIHost::OnOptions)
|
|
EVT_MENU(kDefaultsID, EffectUIHost::OnDefaults)
|
|
EVT_MENU_RANGE(kUserPresetsID, kUserPresetsID + 999, EffectUIHost::OnUserPreset)
|
|
EVT_MENU_RANGE(kDeletePresetID, kDeletePresetID + 999, EffectUIHost::OnDeletePreset)
|
|
EVT_MENU_RANGE(kFactoryPresetsID, kFactoryPresetsID + 999, EffectUIHost::OnFactoryPreset)
|
|
END_EVENT_TABLE()
|
|
|
|
EffectUIHost::EffectUIHost(wxWindow *parent,
|
|
AudacityProject &project,
|
|
Effect *effect,
|
|
EffectUIClientInterface *client)
|
|
: wxDialogWrapper(parent, wxID_ANY, effect->GetName(),
|
|
wxDefaultPosition, wxDefaultSize,
|
|
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMINIMIZE_BOX | wxMAXIMIZE_BOX)
|
|
{
|
|
#if defined(__WXMAC__)
|
|
// Make sure the effect window actually floats above the main window
|
|
[ [((NSView *)GetHandle()) window] setLevel:NSFloatingWindowLevel];
|
|
#endif
|
|
|
|
SetName( effect->GetName() );
|
|
SetExtraStyle(GetExtraStyle() | wxWS_EX_VALIDATE_RECURSIVELY);
|
|
|
|
mParent = parent;
|
|
mEffect = effect;
|
|
mCommand = NULL;
|
|
mClient = client;
|
|
|
|
mProject = &project;
|
|
|
|
mInitialized = false;
|
|
mSupportsRealtime = false;
|
|
|
|
mDisableTransport = false;
|
|
|
|
mEnabled = true;
|
|
|
|
mPlayPos = 0.0;
|
|
mClient->SetHostUI(this);
|
|
}
|
|
|
|
EffectUIHost::EffectUIHost(wxWindow *parent,
|
|
AudacityProject &project,
|
|
AudacityCommand *command,
|
|
EffectUIClientInterface *client)
|
|
: wxDialogWrapper(parent, wxID_ANY, XO("Some Command") /*command->GetName()*/,
|
|
wxDefaultPosition, wxDefaultSize,
|
|
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMINIMIZE_BOX | wxMAXIMIZE_BOX)
|
|
{
|
|
#if defined(__WXMAC__)
|
|
// Make sure the effect window actually floats above the main window
|
|
[ [((NSView *)GetHandle()) window] setLevel:NSFloatingWindowLevel];
|
|
#endif
|
|
|
|
//SetName( command->GetName() );
|
|
SetExtraStyle(wxWS_EX_VALIDATE_RECURSIVELY);
|
|
|
|
mParent = parent;
|
|
mEffect = NULL;
|
|
mCommand = command;
|
|
mClient = client;
|
|
|
|
mProject = &project;
|
|
|
|
mInitialized = false;
|
|
mSupportsRealtime = false;
|
|
|
|
mDisableTransport = false;
|
|
|
|
mEnabled = true;
|
|
|
|
mPlayPos = 0.0;
|
|
mClient->SetHostUI(this);
|
|
}
|
|
|
|
|
|
|
|
|
|
EffectUIHost::~EffectUIHost()
|
|
{
|
|
CleanupRealtime();
|
|
|
|
if (mClient)
|
|
{
|
|
if (mNeedsResume)
|
|
Resume();
|
|
|
|
mClient->CloseUI();
|
|
mClient = NULL;
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// wxWindow implementation
|
|
// ============================================================================
|
|
|
|
bool EffectUIHost::TransferDataToWindow()
|
|
{
|
|
if( mEffect )
|
|
return mEffect->TransferDataToWindow();
|
|
if( mCommand )
|
|
return mCommand->TransferDataToWindow();
|
|
return false;
|
|
}
|
|
|
|
bool EffectUIHost::TransferDataFromWindow()
|
|
{
|
|
if( mEffect)
|
|
return mEffect->TransferDataFromWindow();
|
|
if( mCommand)
|
|
return mCommand->TransferDataFromWindow();
|
|
return false;
|
|
}
|
|
|
|
// ============================================================================
|
|
// wxDialog implementation
|
|
// ============================================================================
|
|
|
|
int EffectUIHost::ShowModal()
|
|
{
|
|
#if defined(__WXMSW__)
|
|
// Swap the Close and Apply buttons
|
|
wxSizer *sz = mApplyBtn->GetContainingSizer();
|
|
wxASSERT(mApplyBtn->GetParent()); // To justify safenew
|
|
wxButton *apply = safenew wxButton(mApplyBtn->GetParent(), wxID_APPLY);
|
|
sz->Replace(mCloseBtn, apply);
|
|
sz->Replace(mApplyBtn, mCloseBtn);
|
|
sz->Layout();
|
|
mApplyBtn->Destroy();
|
|
mApplyBtn = apply;
|
|
mApplyBtn->SetDefault();
|
|
mApplyBtn->SetLabel(wxGetStockLabel(wxID_OK, 0));
|
|
mCloseBtn->SetLabel(wxGetStockLabel(wxID_CANCEL, 0));
|
|
#else
|
|
mApplyBtn->SetLabel(wxGetStockLabel(wxID_OK));
|
|
mCloseBtn->SetLabel(wxGetStockLabel(wxID_CANCEL));
|
|
#endif
|
|
|
|
Layout();
|
|
|
|
return wxDialogWrapper::ShowModal();
|
|
}
|
|
|
|
// ============================================================================
|
|
// EffectUIHost implementation
|
|
// ============================================================================
|
|
|
|
wxPanel *EffectUIHost::BuildButtonBar(wxWindow *parent)
|
|
{
|
|
mSupportsRealtime = mEffect && mEffect->SupportsRealtime();
|
|
mIsGUI = mClient->IsGraphicalUI();
|
|
mIsBatch = (mEffect && mEffect->IsBatchProcessing()) ||
|
|
(mCommand && mCommand->IsBatchProcessing());
|
|
|
|
int margin = 0;
|
|
#if defined(__WXMAC__)
|
|
margin = 3; // I'm sure it's needed because of the order things are created...
|
|
#endif
|
|
|
|
const auto bar = safenew wxPanelWrapper(parent, wxID_ANY);
|
|
|
|
// This fools NVDA into not saying "Panel" when the dialog gets focus
|
|
bar->SetName(TranslatableString::Inaudible);
|
|
bar->SetLabel(TranslatableString::Inaudible);
|
|
|
|
ShuttleGui S{ bar, eIsCreating,
|
|
false /* horizontal */,
|
|
{ -1, -1 } /* minimum size */
|
|
};
|
|
{
|
|
S.SetBorder( margin );
|
|
|
|
if (!mIsGUI)
|
|
{
|
|
mMenuBtn = S.Id( kMenuID )
|
|
.ToolTip(XO("Manage presets and options"))
|
|
.AddButton( XO("&Manage"), wxALIGN_CENTER | wxTOP | wxBOTTOM );
|
|
}
|
|
else
|
|
{
|
|
mMenuBtn = S.Id( kMenuID )
|
|
.ToolTip(XO("Manage presets and options"))
|
|
.Name(XO("&Manage"))
|
|
.AddBitmapButton( CreateBitmap(effect_menu_xpm, true, true) );
|
|
mMenuBtn->SetBitmapPressed(CreateBitmap(effect_menu_xpm, false, true));
|
|
}
|
|
|
|
S.AddSpace( 5, 5 );
|
|
|
|
if (!mIsBatch)
|
|
{
|
|
if (!mIsGUI)
|
|
{
|
|
if (mSupportsRealtime)
|
|
{
|
|
mPlayToggleBtn = S.Id( kPlayID )
|
|
.ToolTip(XO("Start and stop playback"))
|
|
.AddButton( XO("Start &Playback"),
|
|
wxALIGN_CENTER | wxTOP | wxBOTTOM );
|
|
}
|
|
else if (mEffect &&
|
|
(mEffect->GetType() != EffectTypeAnalyze) &&
|
|
(mEffect->GetType() != EffectTypeTool) )
|
|
{
|
|
mPlayToggleBtn = S.Id( kPlayID )
|
|
.ToolTip(XO("Preview effect"))
|
|
.AddButton( XO("&Preview"),
|
|
wxALIGN_CENTER | wxTOP | wxBOTTOM );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mPlayBM = CreateBitmap(effect_play_xpm, true, false);
|
|
mPlayDisabledBM = CreateBitmap(effect_play_disabled_xpm, true, true);
|
|
mStopBM = CreateBitmap(effect_stop_xpm, true, false);
|
|
mStopDisabledBM = CreateBitmap(effect_stop_disabled_xpm, true, false);
|
|
mPlayBtn = S.Id( kPlayID ).AddBitmapButton( mPlayBM );
|
|
mPlayBtn->SetBitmapDisabled(mPlayDisabledBM);
|
|
mPlayBtn->SetBitmapPressed(CreateBitmap(effect_play_xpm, false, true));
|
|
if (!mSupportsRealtime)
|
|
{
|
|
mPlayBtn->SetToolTip(_("Preview effect"));
|
|
#if defined(__WXMAC__)
|
|
mPlayBtn->SetName(_("Preview effect"));
|
|
#else
|
|
mPlayBtn->SetLabel(_("&Preview effect"));
|
|
#endif
|
|
}
|
|
}
|
|
|
|
if (mSupportsRealtime)
|
|
{
|
|
if (!mIsGUI)
|
|
{
|
|
mRewindBtn = S.Id( kRewindID )
|
|
.ToolTip(XO("Skip backward"))
|
|
.AddButton( XO("Skip &Backward"),
|
|
wxALIGN_CENTER | wxTOP | wxBOTTOM );
|
|
}
|
|
else
|
|
{
|
|
mRewindBtn = S.Id( kRewindID )
|
|
.ToolTip(XO("Skip backward"))
|
|
.Name(XO("Skip &Backward"))
|
|
.AddBitmapButton( CreateBitmap(
|
|
effect_rewind_xpm, true, true) );
|
|
mRewindBtn->SetBitmapDisabled(
|
|
CreateBitmap(effect_rewind_disabled_xpm, true, false));
|
|
mRewindBtn->SetBitmapPressed(CreateBitmap(effect_rewind_xpm, false, true));
|
|
}
|
|
|
|
if (!mIsGUI)
|
|
{
|
|
mFFwdBtn = S.Id( kFFwdID )
|
|
.ToolTip(XO("Skip forward"))
|
|
.AddButton( XO("Skip &Forward"),
|
|
wxALIGN_CENTER | wxTOP | wxBOTTOM );
|
|
}
|
|
else
|
|
{
|
|
mFFwdBtn = S.Id( kFFwdID )
|
|
.ToolTip(XO("Skip forward"))
|
|
.Name(XO("Skip &Forward"))
|
|
.AddBitmapButton( CreateBitmap(
|
|
effect_ffwd_xpm, true, true) );
|
|
mFFwdBtn->SetBitmapDisabled(
|
|
CreateBitmap(effect_ffwd_disabled_xpm, true, false));
|
|
mFFwdBtn->SetBitmapPressed(CreateBitmap(effect_ffwd_xpm, false, true));
|
|
}
|
|
|
|
S.AddSpace( 5, 5 );
|
|
|
|
mEnableCb = S.Id( kEnableID )
|
|
.Position(wxALIGN_CENTER | wxTOP | wxBOTTOM)
|
|
.Name(XO("Enable"))
|
|
.AddCheckBox( XO("&Enable"), mEnabled );
|
|
//
|
|
}
|
|
}
|
|
}
|
|
|
|
bar->GetSizer()->SetSizeHints( bar );
|
|
|
|
return bar;
|
|
}
|
|
|
|
bool EffectUIHost::Initialize()
|
|
{
|
|
{
|
|
auto gAudioIO = AudioIO::Get();
|
|
mDisableTransport = !gAudioIO->IsAvailable(mProject);
|
|
mPlaying = gAudioIO->IsStreamActive(); // not exactly right, but will suffice
|
|
mCapturing = gAudioIO->IsStreamActive() && gAudioIO->GetNumCaptureChannels() > 0;
|
|
}
|
|
|
|
EffectPanel *w {};
|
|
ShuttleGui S{ this, eIsCreating };
|
|
{
|
|
S.StartHorizontalLay( wxEXPAND );
|
|
{
|
|
Destroy_ptr<EffectPanel> uw{ safenew EffectPanel( S.GetParent() ) };
|
|
RTL_WORKAROUND(uw.get());
|
|
|
|
// Try to give the window a sensible default/minimum size
|
|
uw->SetMinSize(wxSize(wxMax(600, mParent->GetSize().GetWidth() * 2 / 3),
|
|
mParent->GetSize().GetHeight() / 2));
|
|
|
|
ShuttleGui S1{ uw.get(), eIsCreating };
|
|
if (!mClient->PopulateUI(S1))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
S.Prop( 1 )
|
|
.Position(wxEXPAND)
|
|
.AddWindow((w = uw.release()));
|
|
}
|
|
S.EndHorizontalLay();
|
|
|
|
S.StartPanel();
|
|
{
|
|
const auto bar = BuildButtonBar( S.GetParent() );
|
|
|
|
long buttons;
|
|
if ( mEffect && mEffect->ManualPage().empty() && mEffect->HelpPage().empty()) {
|
|
buttons = eApplyButton | eCloseButton;
|
|
this->SetAcceleratorTable(wxNullAcceleratorTable);
|
|
}
|
|
else {
|
|
buttons = eApplyButton | eCloseButton | eHelpButton;
|
|
wxAcceleratorEntry entries[1];
|
|
#if defined(__WXMAC__)
|
|
// Is there a standard shortcut on Mac?
|
|
#else
|
|
entries[0].Set(wxACCEL_NORMAL, (int) WXK_F1, wxID_HELP);
|
|
#endif
|
|
wxAcceleratorTable accel(1, entries);
|
|
this->SetAcceleratorTable(accel);
|
|
}
|
|
|
|
if (mEffect && mEffect->mUIDebug) {
|
|
buttons |= eDebugButton;
|
|
}
|
|
|
|
S.AddStandardButtons(buttons, bar);
|
|
}
|
|
S.EndPanel();
|
|
}
|
|
|
|
Layout();
|
|
Fit();
|
|
Center();
|
|
|
|
mApplyBtn = (wxButton *) FindWindow(wxID_APPLY);
|
|
mCloseBtn = (wxButton *) FindWindow(wxID_CANCEL);
|
|
|
|
UpdateControls();
|
|
|
|
w->SetAccept(!mIsGUI);
|
|
(!mIsGUI ? w : FindWindow(wxID_APPLY))->SetFocus();
|
|
|
|
LoadUserPresets();
|
|
|
|
InitializeRealtime();
|
|
|
|
SetMinSize(GetSize());
|
|
return true;
|
|
}
|
|
|
|
void EffectUIHost::OnInitDialog(wxInitDialogEvent & evt)
|
|
{
|
|
// Do default handling
|
|
wxDialogWrapper::OnInitDialog(evt);
|
|
|
|
#if wxCHECK_VERSION(3, 0, 0)
|
|
//#warning "check to see if this still needed in wx3"
|
|
#endif
|
|
|
|
// Pure hackage coming down the pike...
|
|
//
|
|
// I have no idea why, but if a wxTextCtrl is the first control in the
|
|
// panel, then its contents will not be automatically selected when the
|
|
// dialog is displayed.
|
|
//
|
|
// So, we do the selection manually.
|
|
wxTextCtrl *focused = wxDynamicCast(FindFocus(), wxTextCtrl);
|
|
if (focused)
|
|
{
|
|
focused->SelectAll();
|
|
}
|
|
}
|
|
|
|
void EffectUIHost::OnErase(wxEraseEvent & WXUNUSED(evt))
|
|
{
|
|
// Ignore it
|
|
}
|
|
|
|
void EffectUIHost::OnPaint(wxPaintEvent & WXUNUSED(evt))
|
|
{
|
|
wxPaintDC dc(this);
|
|
|
|
dc.Clear();
|
|
}
|
|
|
|
void EffectUIHost::OnClose(wxCloseEvent & WXUNUSED(evt))
|
|
{
|
|
DoCancel();
|
|
|
|
CleanupRealtime();
|
|
|
|
Hide();
|
|
|
|
if (mNeedsResume)
|
|
Resume();
|
|
mClient->CloseUI();
|
|
mClient = NULL;
|
|
|
|
Destroy();
|
|
}
|
|
|
|
void EffectUIHost::OnApply(wxCommandEvent & evt)
|
|
{
|
|
auto &project = *mProject;
|
|
|
|
// On wxGTK (wx2.8.12), the default action is still executed even if
|
|
// the button is disabled. This appears to affect all wxDialogs, not
|
|
// just our Effects dialogs. So, this is a only temporary workaround
|
|
// for legacy effects that disable the OK button. Hopefully this has
|
|
// been corrected in wx3.
|
|
if (!FindWindow(wxID_APPLY)->IsEnabled())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Honor the "select all if none" preference...a little hackish, but whatcha gonna do...
|
|
if (!mIsBatch &&
|
|
mEffect &&
|
|
mEffect->GetType() != EffectTypeGenerate &&
|
|
mEffect->GetType() != EffectTypeTool &&
|
|
ViewInfo::Get( project ).selectedRegion.isPoint())
|
|
{
|
|
auto flags = AlwaysEnabledFlag;
|
|
bool allowed =
|
|
MenuManager::Get( project ).ReportIfActionNotAllowed(
|
|
mEffect->GetName(),
|
|
flags,
|
|
WaveTracksSelectedFlag() | TimeSelectedFlag());
|
|
if (!allowed)
|
|
return;
|
|
}
|
|
|
|
if (!mClient->ValidateUI())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// This will take care of calling TransferDataFromWindow() for an effect.
|
|
if (mEffect && !mEffect->SaveUserPreset(mEffect->GetCurrentSettingsGroup()))
|
|
{
|
|
return;
|
|
}
|
|
// This will take care of calling TransferDataFromWindow() for a command.
|
|
if (mCommand ){
|
|
wxString params;
|
|
mCommand->GetAutomationParameters( params );
|
|
}
|
|
|
|
if( mEffect )
|
|
mEffect->mUIResultID = evt.GetId();
|
|
|
|
if (IsModal())
|
|
{
|
|
mDismissed = true;
|
|
|
|
EndModal(true);
|
|
|
|
Close();
|
|
|
|
return;
|
|
}
|
|
|
|
// Progress dialog no longer yields, so this "shouldn't" be necessary (yet to be proven
|
|
// for sure), but it is a nice visual cue that something is going on.
|
|
mApplyBtn->Disable();
|
|
auto cleanup = finally( [&] { mApplyBtn->Enable(); } );
|
|
|
|
if( mEffect ) {
|
|
CommandContext context( project );
|
|
// This is absolute hackage...but easy and I can't think of another way just now.
|
|
//
|
|
// It should callback to the EffectManager to kick off the processing
|
|
EffectUI::DoEffect(mEffect->GetID(), context,
|
|
EffectManager::kConfigured);
|
|
}
|
|
|
|
if( mCommand )
|
|
// PRL: I don't like the global and would rather pass *mProject!
|
|
// But I am preserving old behavior
|
|
mCommand->Apply( CommandContext{ project } );
|
|
}
|
|
|
|
void EffectUIHost::DoCancel()
|
|
{
|
|
if (!mDismissed) {
|
|
if( mEffect )
|
|
mEffect->mUIResultID = wxID_CANCEL;
|
|
|
|
if (IsModal())
|
|
EndModal(false);
|
|
else
|
|
Hide();
|
|
|
|
mDismissed = true;
|
|
}
|
|
}
|
|
|
|
void EffectUIHost::OnCancel(wxCommandEvent & WXUNUSED(evt))
|
|
{
|
|
DoCancel();
|
|
Close();
|
|
}
|
|
|
|
void EffectUIHost::OnHelp(wxCommandEvent & WXUNUSED(event))
|
|
{
|
|
if (mEffect && mEffect->GetFamily() == NYQUISTEFFECTS_FAMILY && (mEffect->ManualPage().empty())) {
|
|
// Old ShowHelp required when there is no on-line manual.
|
|
// Always use default web browser to allow full-featured HTML pages.
|
|
HelpSystem::ShowHelp(FindWindow(wxID_HELP), mEffect->HelpPage(), wxEmptyString, true, true);
|
|
}
|
|
else if( mEffect )
|
|
{
|
|
// otherwise use the NEW ShowHelp
|
|
HelpSystem::ShowHelp(FindWindow(wxID_HELP), mEffect->ManualPage(), true);
|
|
}
|
|
}
|
|
|
|
void EffectUIHost::OnDebug(wxCommandEvent & evt)
|
|
{
|
|
OnApply(evt);
|
|
if( mEffect )
|
|
mEffect->mUIResultID = evt.GetId();
|
|
}
|
|
|
|
void EffectUIHost::OnMenu(wxCommandEvent & WXUNUSED(evt))
|
|
{
|
|
wxMenu menu;
|
|
if( !mEffect )
|
|
return;
|
|
|
|
LoadUserPresets();
|
|
|
|
if (mUserPresets.size() == 0)
|
|
{
|
|
menu.Append(kUserPresetsDummyID, _("User Presets"))->Enable(false);
|
|
}
|
|
else
|
|
{
|
|
auto sub = std::make_unique<wxMenu>();
|
|
for (size_t i = 0, cnt = mUserPresets.size(); i < cnt; i++)
|
|
{
|
|
sub->Append(kUserPresetsID + i, mUserPresets[i]);
|
|
}
|
|
menu.Append(0, _("User Presets"), sub.release());
|
|
}
|
|
|
|
menu.Append(kSaveAsID, _("Save Preset..."));
|
|
|
|
if (mUserPresets.size() == 0)
|
|
{
|
|
menu.Append(kDeletePresetDummyID, _("Delete Preset"))->Enable(false);
|
|
}
|
|
else
|
|
{
|
|
auto sub = std::make_unique<wxMenu>();
|
|
for (size_t i = 0, cnt = mUserPresets.size(); i < cnt; i++)
|
|
{
|
|
sub->Append(kDeletePresetID + i, mUserPresets[i]);
|
|
}
|
|
menu.Append(0, _("Delete Preset"), sub.release());
|
|
}
|
|
|
|
menu.AppendSeparator();
|
|
|
|
auto factory = mEffect->GetFactoryPresets();
|
|
|
|
{
|
|
auto sub = std::make_unique<wxMenu>();
|
|
sub->Append(kDefaultsID, _("Defaults"));
|
|
if (factory.size() > 0)
|
|
{
|
|
sub->AppendSeparator();
|
|
for (size_t i = 0, cnt = factory.size(); i < cnt; i++)
|
|
{
|
|
auto label = factory[i];
|
|
if (label.empty())
|
|
{
|
|
label = _("None");
|
|
}
|
|
|
|
sub->Append(kFactoryPresetsID + i, label);
|
|
}
|
|
}
|
|
menu.Append(0, _("Factory Presets"), sub.release());
|
|
}
|
|
|
|
menu.AppendSeparator();
|
|
menu.Append(kImportID, _("Import..."))->Enable(mClient->CanExportPresets());
|
|
menu.Append(kExportID, _("Export..."))->Enable(mClient->CanExportPresets());
|
|
menu.AppendSeparator();
|
|
menu.Append(kOptionsID, _("Options..."))->Enable(mClient->HasOptions());
|
|
menu.AppendSeparator();
|
|
|
|
{
|
|
auto sub = std::make_unique<wxMenu>();
|
|
|
|
sub->Append(kDummyID, wxString::Format(_("Type: %s"),
|
|
::wxGetTranslation( mEffect->GetFamily().Translation() )));
|
|
sub->Append(kDummyID, wxString::Format(_("Name: %s"), mEffect->GetName().Translation()));
|
|
sub->Append(kDummyID, wxString::Format(_("Version: %s"), mEffect->GetVersion()));
|
|
sub->Append(kDummyID, wxString::Format(_("Vendor: %s"), mEffect->GetVendor().Translation()));
|
|
sub->Append(kDummyID, wxString::Format(_("Description: %s"), mEffect->GetDescription().Translation()));
|
|
|
|
menu.Append(0, _("About"), sub.release());
|
|
}
|
|
|
|
wxWindow *btn = FindWindow(kMenuID);
|
|
wxRect r = btn->GetRect();
|
|
btn->PopupMenu(&menu, r.GetLeft(), r.GetBottom());
|
|
}
|
|
|
|
void EffectUIHost::Resume()
|
|
{
|
|
if (!mClient->ValidateUI()) {
|
|
// If we're previewing we should still be able to stop playback
|
|
// so don't disable transport buttons.
|
|
// mEffect->EnableApply(false); // currently this would also disable transport buttons.
|
|
// The preferred behaviour is currently undecided, so for now
|
|
// just disallow enabling until settings are valid.
|
|
mEnabled = false;
|
|
mEnableCb->SetValue(mEnabled);
|
|
return;
|
|
}
|
|
mEffect->RealtimeResume();
|
|
}
|
|
|
|
void EffectUIHost::OnEnable(wxCommandEvent & WXUNUSED(evt))
|
|
{
|
|
mEnabled = mEnableCb->GetValue();
|
|
|
|
if (mEnabled) {
|
|
Resume();
|
|
mNeedsResume = false;
|
|
}
|
|
else
|
|
{
|
|
mEffect->RealtimeSuspend();
|
|
mNeedsResume = true;
|
|
}
|
|
|
|
UpdateControls();
|
|
}
|
|
|
|
void EffectUIHost::OnPlay(wxCommandEvent & WXUNUSED(evt))
|
|
{
|
|
if (!mSupportsRealtime)
|
|
{
|
|
if (!mClient->ValidateUI() || !mEffect->TransferDataFromWindow())
|
|
{
|
|
return;
|
|
}
|
|
|
|
mEffect->Preview(false);
|
|
|
|
return;
|
|
}
|
|
|
|
if (mPlaying)
|
|
{
|
|
auto gAudioIO = AudioIO::Get();
|
|
mPlayPos = gAudioIO->GetStreamTime();
|
|
auto &projectAudioManager = ProjectAudioManager::Get( *mProject );
|
|
projectAudioManager.Stop();
|
|
}
|
|
else
|
|
{
|
|
auto &viewInfo = ViewInfo::Get( *mProject );
|
|
const auto &selectedRegion = viewInfo.selectedRegion;
|
|
const auto &playRegion = viewInfo.playRegion;
|
|
if ( playRegion.Locked() )
|
|
{
|
|
mRegion.setTimes(playRegion.GetStart(), playRegion.GetEnd());
|
|
mPlayPos = mRegion.t0();
|
|
}
|
|
else if (selectedRegion.t0() != mRegion.t0() ||
|
|
selectedRegion.t1() != mRegion.t1())
|
|
{
|
|
mRegion = selectedRegion;
|
|
mPlayPos = mRegion.t0();
|
|
}
|
|
|
|
if (mPlayPos > mRegion.t1())
|
|
{
|
|
mPlayPos = mRegion.t1();
|
|
}
|
|
|
|
auto &projectAudioManager = ProjectAudioManager::Get( *mProject );
|
|
projectAudioManager.PlayPlayRegion(
|
|
SelectedRegion(mPlayPos, mRegion.t1()),
|
|
DefaultPlayOptions( *mProject ),
|
|
PlayMode::normalPlay );
|
|
}
|
|
}
|
|
|
|
void EffectUIHost::OnRewind(wxCommandEvent & WXUNUSED(evt))
|
|
{
|
|
if (mPlaying)
|
|
{
|
|
auto gAudioIO = AudioIO::Get();
|
|
double seek;
|
|
gPrefs->Read(wxT("/AudioIO/SeekShortPeriod"), &seek, 1.0);
|
|
|
|
double pos = gAudioIO->GetStreamTime();
|
|
if (pos - seek < mRegion.t0())
|
|
{
|
|
seek = pos - mRegion.t0();
|
|
}
|
|
|
|
gAudioIO->SeekStream(-seek);
|
|
}
|
|
else
|
|
{
|
|
mPlayPos = mRegion.t0();
|
|
}
|
|
}
|
|
|
|
void EffectUIHost::OnFFwd(wxCommandEvent & WXUNUSED(evt))
|
|
{
|
|
if (mPlaying)
|
|
{
|
|
double seek;
|
|
gPrefs->Read(wxT("/AudioIO/SeekShortPeriod"), &seek, 1.0);
|
|
|
|
auto gAudioIO = AudioIO::Get();
|
|
double pos = gAudioIO->GetStreamTime();
|
|
if (mRegion.t0() < mRegion.t1() && pos + seek > mRegion.t1())
|
|
{
|
|
seek = mRegion.t1() - pos;
|
|
}
|
|
|
|
gAudioIO->SeekStream(seek);
|
|
}
|
|
else
|
|
{
|
|
// It allows to play past end of selection...probably useless
|
|
mPlayPos = mRegion.t1();
|
|
}
|
|
}
|
|
|
|
void EffectUIHost::OnPlayback(wxCommandEvent & evt)
|
|
{
|
|
evt.Skip();
|
|
|
|
if (evt.GetInt() != 0)
|
|
{
|
|
if (evt.GetEventObject() != mProject)
|
|
{
|
|
mDisableTransport = true;
|
|
}
|
|
else
|
|
{
|
|
mPlaying = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mDisableTransport = false;
|
|
mPlaying = false;
|
|
}
|
|
|
|
if (mPlaying)
|
|
{
|
|
mRegion = ViewInfo::Get( *mProject ).selectedRegion;
|
|
mPlayPos = mRegion.t0();
|
|
}
|
|
|
|
UpdateControls();
|
|
}
|
|
|
|
void EffectUIHost::OnCapture(wxCommandEvent & evt)
|
|
{
|
|
evt.Skip();
|
|
|
|
if (evt.GetInt() != 0)
|
|
{
|
|
if (evt.GetEventObject() != mProject)
|
|
{
|
|
mDisableTransport = true;
|
|
}
|
|
else
|
|
{
|
|
mCapturing = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mDisableTransport = false;
|
|
mCapturing = false;
|
|
}
|
|
|
|
UpdateControls();
|
|
}
|
|
|
|
void EffectUIHost::OnUserPreset(wxCommandEvent & evt)
|
|
{
|
|
int preset = evt.GetId() - kUserPresetsID;
|
|
|
|
mEffect->LoadUserPreset(mEffect->GetUserPresetsGroup(mUserPresets[preset]));
|
|
|
|
return;
|
|
}
|
|
|
|
void EffectUIHost::OnFactoryPreset(wxCommandEvent & evt)
|
|
{
|
|
mEffect->LoadFactoryPreset(evt.GetId() - kFactoryPresetsID);
|
|
|
|
return;
|
|
}
|
|
|
|
void EffectUIHost::OnDeletePreset(wxCommandEvent & evt)
|
|
{
|
|
auto preset = mUserPresets[evt.GetId() - kDeletePresetID];
|
|
|
|
int res = AudacityMessageBox(
|
|
XO("Are you sure you want to delete \"%s\"?").Format( preset ),
|
|
XO("Delete Preset"),
|
|
wxICON_QUESTION | wxYES_NO);
|
|
if (res == wxYES)
|
|
{
|
|
mEffect->RemovePrivateConfigSubgroup(mEffect->GetUserPresetsGroup(preset));
|
|
}
|
|
|
|
LoadUserPresets();
|
|
|
|
return;
|
|
}
|
|
|
|
void EffectUIHost::OnSaveAs(wxCommandEvent & WXUNUSED(evt))
|
|
{
|
|
wxTextCtrl *text;
|
|
wxString name;
|
|
wxDialogWrapper dlg(this, wxID_ANY, XO("Save Preset"));
|
|
|
|
ShuttleGui S(&dlg, eIsCreating);
|
|
|
|
S.StartPanel();
|
|
{
|
|
S.StartVerticalLay(1);
|
|
{
|
|
S.StartHorizontalLay(wxALIGN_LEFT, 0);
|
|
{
|
|
text = S.AddTextBox(XO("Preset name:"), name, 30);
|
|
}
|
|
S.EndHorizontalLay();
|
|
S.SetBorder(10);
|
|
S.AddStandardButtons();
|
|
}
|
|
S.EndVerticalLay();
|
|
}
|
|
S.EndPanel();
|
|
|
|
dlg.SetSize(dlg.GetSizer()->GetMinSize());
|
|
dlg.Center();
|
|
dlg.Fit();
|
|
|
|
while (true)
|
|
{
|
|
int rc = dlg.ShowModal();
|
|
|
|
if (rc != wxID_OK)
|
|
{
|
|
break;
|
|
}
|
|
|
|
name = text->GetValue();
|
|
if (name.empty())
|
|
{
|
|
AudacityMessageDialog md(
|
|
this,
|
|
XO("You must specify a name"),
|
|
XO("Save Preset") );
|
|
md.Center();
|
|
md.ShowModal();
|
|
continue;
|
|
}
|
|
|
|
if ( make_iterator_range( mUserPresets ).contains( name ) )
|
|
{
|
|
AudacityMessageDialog md(
|
|
this,
|
|
XO("Preset already exists.\n\nReplace?"),
|
|
XO("Save Preset"),
|
|
wxYES_NO | wxCANCEL | wxICON_EXCLAMATION );
|
|
md.Center();
|
|
int choice = md.ShowModal();
|
|
if (choice == wxID_CANCEL)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (choice == wxID_NO)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
mEffect->SaveUserPreset(mEffect->GetUserPresetsGroup(name));
|
|
LoadUserPresets();
|
|
|
|
break;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void EffectUIHost::OnImport(wxCommandEvent & WXUNUSED(evt))
|
|
{
|
|
mClient->ImportPresets();
|
|
|
|
LoadUserPresets();
|
|
|
|
return;
|
|
}
|
|
|
|
void EffectUIHost::OnExport(wxCommandEvent & WXUNUSED(evt))
|
|
{
|
|
// may throw
|
|
// exceptions are handled in AudacityApp::OnExceptionInMainLoop
|
|
mClient->ExportPresets();
|
|
|
|
return;
|
|
}
|
|
|
|
void EffectUIHost::OnOptions(wxCommandEvent & WXUNUSED(evt))
|
|
{
|
|
mClient->ShowOptions();
|
|
|
|
return;
|
|
}
|
|
|
|
void EffectUIHost::OnDefaults(wxCommandEvent & WXUNUSED(evt))
|
|
{
|
|
mEffect->LoadFactoryDefaults();
|
|
|
|
return;
|
|
}
|
|
|
|
wxBitmap EffectUIHost::CreateBitmap(const char * const xpm[], bool up, bool pusher)
|
|
{
|
|
wxMemoryDC dc;
|
|
wxBitmap pic(xpm);
|
|
|
|
wxBitmap mod(pic.GetWidth() + 6, pic.GetHeight() + 6, 24);
|
|
dc.SelectObject(mod);
|
|
|
|
#if defined(__WXGTK__)
|
|
wxColour newColour = wxSystemSettings::GetColour(wxSYS_COLOUR_BACKGROUND);
|
|
#else
|
|
wxColour newColour = wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE);
|
|
#endif
|
|
|
|
dc.SetBackground(wxBrush(newColour));
|
|
dc.Clear();
|
|
|
|
int offset = 3;
|
|
if (pusher)
|
|
{
|
|
if (!up)
|
|
{
|
|
offset += 1;
|
|
}
|
|
}
|
|
|
|
dc.DrawBitmap(pic, offset, offset, true);
|
|
|
|
dc.SelectObject(wxNullBitmap);
|
|
|
|
return mod;
|
|
}
|
|
|
|
void EffectUIHost::UpdateControls()
|
|
{
|
|
if (mIsBatch)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (mCapturing || mDisableTransport)
|
|
{
|
|
// Don't allow focus to get trapped
|
|
wxWindow *focus = FindFocus();
|
|
if (focus == mRewindBtn || focus == mFFwdBtn || focus == mPlayBtn || focus == mEnableCb)
|
|
{
|
|
mCloseBtn->SetFocus();
|
|
}
|
|
}
|
|
|
|
mApplyBtn->Enable(!mCapturing);
|
|
if (mEffect && (mEffect->GetType() != EffectTypeAnalyze) && (mEffect->GetType() != EffectTypeTool) )
|
|
{
|
|
(!mIsGUI ? mPlayToggleBtn : mPlayBtn)->Enable(!(mCapturing || mDisableTransport));
|
|
}
|
|
|
|
if (mSupportsRealtime)
|
|
{
|
|
mRewindBtn->Enable(!(mCapturing || mDisableTransport));
|
|
mFFwdBtn->Enable(!(mCapturing || mDisableTransport));
|
|
mEnableCb->Enable(!(mCapturing || mDisableTransport));
|
|
|
|
wxBitmapButton *bb;
|
|
|
|
if (mPlaying)
|
|
{
|
|
if (!mIsGUI)
|
|
{
|
|
/* i18n-hint: The access key "&P" should be the same in
|
|
"Stop &Playback" and "Start &Playback" */
|
|
mPlayToggleBtn->SetLabel(_("Stop &Playback"));
|
|
mPlayToggleBtn->Refresh();
|
|
}
|
|
else
|
|
{
|
|
bb = (wxBitmapButton *) mPlayBtn;
|
|
bb->SetBitmapLabel(mStopBM);
|
|
bb->SetBitmapDisabled(mStopDisabledBM);
|
|
bb->SetToolTip(_("Stop"));
|
|
#if defined(__WXMAC__)
|
|
bb->SetName(_("Stop &Playback"));
|
|
#else
|
|
bb->SetLabel(_("Stop &Playback"));
|
|
#endif
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!mIsGUI)
|
|
{
|
|
/* i18n-hint: The access key "&P" should be the same in
|
|
"Stop &Playback" and "Start &Playback" */
|
|
mPlayToggleBtn->SetLabel(_("Start &Playback"));
|
|
mPlayToggleBtn->Refresh();
|
|
}
|
|
else
|
|
{
|
|
bb = (wxBitmapButton *) mPlayBtn;
|
|
bb->SetBitmapLabel(mPlayBM);
|
|
bb->SetBitmapDisabled(mPlayDisabledBM);
|
|
bb->SetToolTip(_("Play"));
|
|
#if defined(__WXMAC__)
|
|
bb->SetName(_("Start &Playback"));
|
|
#else
|
|
bb->SetLabel(_("Start &Playback"));
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void EffectUIHost::LoadUserPresets()
|
|
{
|
|
mUserPresets.clear();
|
|
|
|
if( mEffect )
|
|
mEffect->GetPrivateConfigSubgroups(mEffect->GetUserPresetsGroup(wxEmptyString), mUserPresets);
|
|
|
|
std::sort( mUserPresets.begin(), mUserPresets.end() );
|
|
|
|
return;
|
|
}
|
|
|
|
void EffectUIHost::InitializeRealtime()
|
|
{
|
|
if (mSupportsRealtime && !mInitialized)
|
|
{
|
|
RealtimeEffectManager::Get().RealtimeAddEffect(mEffect);
|
|
|
|
wxTheApp->Bind(EVT_AUDIOIO_PLAYBACK,
|
|
&EffectUIHost::OnPlayback,
|
|
this);
|
|
|
|
wxTheApp->Bind(EVT_AUDIOIO_CAPTURE,
|
|
&EffectUIHost::OnCapture,
|
|
this);
|
|
|
|
mInitialized = true;
|
|
}
|
|
}
|
|
|
|
void EffectUIHost::CleanupRealtime()
|
|
{
|
|
if (mSupportsRealtime && mInitialized)
|
|
{
|
|
RealtimeEffectManager::Get().RealtimeRemoveEffect(mEffect);
|
|
|
|
mInitialized = false;
|
|
}
|
|
}
|
|
|
|
wxDialog *EffectUI::DialogFactory( wxWindow &parent, EffectHostInterface *pHost,
|
|
EffectUIClientInterface *client)
|
|
{
|
|
auto pEffect = dynamic_cast< Effect* >( pHost );
|
|
if ( ! pEffect )
|
|
return nullptr;
|
|
|
|
// Make sure there is an associated project, whose lifetime will
|
|
// govern the lifetime of the dialog, even when the dialog is
|
|
// non-modal, as for realtime effects
|
|
auto project = FindProjectFromWindow(&parent);
|
|
if ( !project )
|
|
return nullptr;
|
|
|
|
Destroy_ptr<EffectUIHost> dlg{
|
|
safenew EffectUIHost{ &parent, *project, pEffect, client} };
|
|
|
|
if (dlg->Initialize())
|
|
{
|
|
// release() is safe because parent will own it
|
|
return dlg.release();
|
|
}
|
|
|
|
return nullptr;
|
|
};
|
|
|
|
#include "../MissingAliasFileDialog.h"
|
|
#include "../PluginManager.h"
|
|
#include "../ProjectSettings.h"
|
|
#include "../ProjectWindow.h"
|
|
#include "../SelectUtilities.h"
|
|
#include "../TrackPanel.h"
|
|
#include "../WaveTrack.h"
|
|
#include "../commands/CommandManager.h"
|
|
|
|
/// DoEffect() takes a PluginID and executes the associated 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'.
|
|
|
|
/* static */ bool EffectUI::DoEffect(
|
|
const PluginID & ID, const CommandContext &context, unsigned flags )
|
|
{
|
|
AudacityProject &project = context.project;
|
|
const auto &settings = ProjectSettings::Get( project );
|
|
auto &tracks = TrackList::Get( project );
|
|
auto &trackPanel = TrackPanel::Get( project );
|
|
auto &trackFactory = TrackFactory::Get( project );
|
|
auto rate = settings.GetRate();
|
|
auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;
|
|
auto &commandManager = CommandManager::Get( project );
|
|
auto &window = ProjectWindow::Get( project );
|
|
|
|
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 & EffectManager::kConfigured)
|
|
{
|
|
ProjectAudioManager::Get( project ).Stop();
|
|
SelectUtilities::SelectAllIfNone( project );
|
|
}
|
|
|
|
MissingAliasFilesDialog::SetShouldShow(true);
|
|
|
|
auto nTracksOriginally = tracks.size();
|
|
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.
|
|
MenuManager::Get(project).UpdateMenus( 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();
|
|
|
|
em.SetSkipStateFlag( false );
|
|
if (auto effect = em.GetEffect(ID)) {
|
|
#if defined(EXPERIMENTAL_EFFECTS_RACK)
|
|
if (effect->SupportsRealtime())
|
|
{
|
|
EffectRack::Get( context.project ).Add(effect);
|
|
}
|
|
#endif
|
|
success = effect->DoEffect(
|
|
rate,
|
|
&tracks,
|
|
&trackFactory,
|
|
selectedRegion,
|
|
&window,
|
|
(flags & EffectManager::kConfigured) == 0
|
|
? DialogFactory
|
|
: nullptr
|
|
);
|
|
}
|
|
else
|
|
success = false;
|
|
|
|
if (!success)
|
|
return false;
|
|
|
|
if (em.GetSkipStateFlag())
|
|
flags = flags | EffectManager::kSkipState;
|
|
|
|
if (!(flags & EffectManager::kSkipState))
|
|
{
|
|
auto shortDesc = em.GetCommandName(ID);
|
|
auto longDesc = em.GetCommandDescription(ID);
|
|
ProjectHistory::Get( project ).PushState(longDesc, shortDesc);
|
|
}
|
|
|
|
if (!(flags & EffectManager::kDontRepeatLast))
|
|
{
|
|
// Only remember a successful effect, don't remember insert,
|
|
// or analyze effects.
|
|
if (type == EffectTypeProcess) {
|
|
auto shortDesc = em.GetCommandName(ID);
|
|
MenuManager::Get(project).mLastEffect = ID;
|
|
/* i18n-hint: %s will be the name of the effect which will be
|
|
* repeated if this menu item is chosen */
|
|
auto lastEffectDesc = XO("Repeat %s").Format( 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))
|
|
window.DoZoomFit();
|
|
// trackPanel->Refresh(false);
|
|
}
|
|
|
|
// PRL: RedrawProject explicitly because sometimes history push is skipped
|
|
window.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( tracks.size() > nTracksOriginally ){
|
|
// 0.0 is min scroll position, 1.0 is max scroll position.
|
|
trackPanel.VerticalScroll( 1.0 );
|
|
}
|
|
else {
|
|
auto pTrack = *tracks.Selected().begin();
|
|
if (!pTrack)
|
|
pTrack = *tracks.Any().begin();
|
|
if (pTrack) {
|
|
TrackFocus::Get(project).Set(pTrack);
|
|
pTrack->EnsureVisible();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
BEGIN_EVENT_TABLE(EffectDialog, wxDialogWrapper)
|
|
EVT_BUTTON(wxID_OK, EffectDialog::OnOk)
|
|
END_EVENT_TABLE()
|
|
|
|
EffectDialog::EffectDialog(wxWindow * parent,
|
|
const TranslatableString & title,
|
|
int type,
|
|
int flags,
|
|
int additionalButtons)
|
|
: wxDialogWrapper(parent, wxID_ANY, title, wxDefaultPosition, wxDefaultSize, flags)
|
|
{
|
|
mType = type;
|
|
mAdditionalButtons = additionalButtons;
|
|
}
|
|
|
|
void EffectDialog::Init()
|
|
{
|
|
long buttons = eOkButton;
|
|
if ((mType != EffectTypeAnalyze) && (mType != EffectTypeTool))
|
|
{
|
|
buttons |= eCancelButton;
|
|
if (mType == EffectTypeProcess)
|
|
{
|
|
buttons |= ePreviewButton;
|
|
}
|
|
}
|
|
|
|
ShuttleGui S(this, eIsCreating);
|
|
|
|
S.SetBorder(5);
|
|
S.StartVerticalLay(true);
|
|
{
|
|
PopulateOrExchange(S);
|
|
S.AddStandardButtons(buttons|mAdditionalButtons);
|
|
}
|
|
S.EndVerticalLay();
|
|
|
|
Layout();
|
|
Fit();
|
|
SetMinSize(GetSize());
|
|
Center();
|
|
}
|
|
|
|
/// This is a virtual function which will be overridden to
|
|
/// provide the actual parameters that we want for each
|
|
/// kind of dialog.
|
|
void EffectDialog::PopulateOrExchange(ShuttleGui & WXUNUSED(S))
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool EffectDialog::TransferDataToWindow()
|
|
{
|
|
ShuttleGui S(this, eIsSettingToDialog);
|
|
PopulateOrExchange(S);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool EffectDialog::TransferDataFromWindow()
|
|
{
|
|
ShuttleGui S(this, eIsGettingFromDialog);
|
|
PopulateOrExchange(S);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool EffectDialog::Validate()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void EffectDialog::OnPreview(wxCommandEvent & WXUNUSED(evt))
|
|
{
|
|
return;
|
|
}
|
|
|
|
void EffectDialog::OnOk(wxCommandEvent & WXUNUSED(evt))
|
|
{
|
|
// On wxGTK (wx2.8.12), the default action is still executed even if
|
|
// the button is disabled. This appears to affect all wxDialogs, not
|
|
// just our Effects dialogs. So, this is a only temporary workaround
|
|
// for legacy effects that disable the OK button. Hopefully this has
|
|
// been corrected in wx3.
|
|
if (FindWindow(wxID_OK)->IsEnabled() && Validate() && TransferDataFromWindow())
|
|
{
|
|
EndModal(true);
|
|
}
|
|
|
|
return;
|
|
}
|