/********************************************************************** 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 "EffectUI.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 #include #include #include #include #include #include #include #include #include #include #include #include #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(wxVERTICAL); mPanel = safenew wxPanelWrapper(this, wxID_ANY); bs->Add(mPanel, 1, wxEXPAND); SetSizer(bs.release()); } { auto bs = std::make_unique(wxVERTICAL); { auto hs = std::make_unique(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(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) // This is like a rollback of state ProjectHistory::Get( *project ).SetStateTo( state, false ); } ); 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(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(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(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(evt.GetEventObject()); evt.Skip(); int index = GetEffectIndex(btn); if (index <= 0) { return; } MoveRowUp(index); } void EffectRack::OnDown(wxCommandEvent & evt) { wxBitmapButton *btn = static_cast(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(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(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 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 "../prefs/GUISettings.h" // for RTL_WORKAROUND #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 #include #include #include #include #include #include #include #if defined(__WXMAC__) #include #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( XXO("&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( XXO("Start &Playback"), wxALIGN_CENTER | wxTOP | wxBOTTOM ); } else if (mEffect && (mEffect->GetType() != EffectTypeAnalyze) && (mEffect->GetType() != EffectTypeTool) ) { mPlayToggleBtn = S.Id( kPlayID ) .ToolTip(XO("Preview effect")) .AddButton( XXO("&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( XXO("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( XXO("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( XXO("&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 && !gAudioIO->IsMonitoring(); } EffectPanel *w {}; ShuttleGui S{ this, eIsCreating }; { S.StartHorizontalLay( wxEXPAND ); { Destroy_ptr 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(); 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(); 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(); 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(); 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; } RealtimeEffectManager::Get().RealtimeResumeOne( *mEffect ); } void EffectUIHost::OnEnable(wxCommandEvent & WXUNUSED(evt)) { mEnabled = mEnableCb->GetValue(); if (mEnabled) { Resume(); mNeedsResume = false; } else { RealtimeEffectManager::Get().RealtimeSuspendOne( *mEffect ); 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(XXO("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 dlg{ safenew EffectUIHost{ &parent, *project, pEffect, client} }; if (dlg->Initialize()) { // release() is safe because parent will own it return dlg.release(); } return nullptr; }; #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 = WaveTrackFactory::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(); //Don't Select All if repeating Generator Effect if (!(flags & EffectManager::kConfigured)) { SelectUtilities::SelectAllIfNone(project); } } 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 effect->SetUIFlags(flags); 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)) { // Remember a successful generator, effect, analyzer, or tool Process auto shortDesc = em.GetCommandName(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); auto& menuManager = MenuManager::Get(project); switch ( type ) { case EffectTypeGenerate: commandManager.Modify(wxT("RepeatLastGenerator"), lastEffectDesc); menuManager.mLastGenerator = ID; menuManager.mRepeatGeneratorFlags = EffectManager::kConfigured; break; case EffectTypeProcess: commandManager.Modify(wxT("RepeatLastEffect"), lastEffectDesc); menuManager.mLastEffect = ID; menuManager.mRepeatEffectFlags = EffectManager::kConfigured; break; case EffectTypeAnalyze: commandManager.Modify(wxT("RepeatLastAnalyzer"), lastEffectDesc); menuManager.mLastAnalyzer = ID; menuManager.mLastAnalyzerRegistration = MenuCreator::repeattypeplugin; menuManager.mRepeatAnalyzerFlags = EffectManager::kConfigured; break; case EffectTypeTool: commandManager.Modify(wxT("RepeatLastTool"), lastEffectDesc); menuManager.mLastTool = ID; menuManager.mLastToolRegistration = MenuCreator::repeattypeplugin; menuManager.mRepeatToolFlags = EffectManager::kConfigured; if (shortDesc == NYQUIST_PROMPT_NAME) { menuManager.mRepeatToolFlags = EffectManager::kRepeatNyquistPrompt; //Nyquist Prompt is not configured } break; } } //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; }