1 /**********************************************************************
2 
3   Audacity: A Digital Audio Editor
4 
5   EffectUI.cpp
6 
7   Leland Lucius
8 
9   Audacity(R) is copyright (c) 1999-2008 Audacity Team.
10   License: GPL v2.  See License.txt.
11 
12 **********************************************************************/
13 
14 
15 #include "EffectUI.h"
16 
17 #include "widgets/BasicMenu.h"
18 #include "Effect.h"
19 #include "EffectManager.h"
20 #include "../ProjectHistory.h"
21 #include "../ProjectWindowBase.h"
22 #include "../TrackPanelAx.h"
23 #include "RealtimeEffectManager.h"
24 #include "widgets/wxWidgetsWindowPlacement.h"
25 
26 #if defined(EXPERIMENTAL_EFFECTS_RACK)
27 
28 #include "../UndoManager.h"
29 
30 #include <wx/dcmemory.h>
31 #include <wx/defs.h>
32 #include <wx/bmpbuttn.h>
33 #include <wx/button.h>
34 #include <wx/frame.h>
35 #include <wx/image.h>
36 #include <wx/imaglist.h>
37 #include <wx/settings.h>
38 #include <wx/sizer.h>
39 #include <wx/statline.h>
40 #include <wx/stattext.h>
41 #include <wx/timer.h>
42 #include <wx/tglbtn.h>
43 
44 #include "../commands/CommandContext.h"
45 #include "../Prefs.h"
46 #include "../Project.h"
47 #include "../widgets/wxPanelWrapper.h"
48 
49 #include "../../images/EffectRack/EffectRack.h"
50 
51 #define COL_POWER    0
52 #define COL_EDITOR   1
53 #define COL_UP       2
54 #define COL_DOWN     3
55 #define COL_FAV      4
56 #define COL_REMOVE   5
57 #define COL_NAME     6
58 #define NUMCOLS      7
59 
60 #define ID_BASE      20000
61 #define ID_RANGE     100
62 #define ID_POWER     (ID_BASE + (COL_POWER * ID_RANGE))
63 #define ID_EDITOR    (ID_BASE + (COL_EDITOR * ID_RANGE))
64 #define ID_UP        (ID_BASE + (COL_UP * ID_RANGE))
65 #define ID_DOWN      (ID_BASE + (COL_DOWN * ID_RANGE))
66 #define ID_FAV       (ID_BASE + (COL_FAV * ID_RANGE))
67 #define ID_REMOVE    (ID_BASE + (COL_REMOVE * ID_RANGE))
68 #define ID_NAME      (ID_BASE + (COL_NAME * ID_RANGE))
69 
BEGIN_EVENT_TABLE(EffectRack,wxFrame)70 BEGIN_EVENT_TABLE(EffectRack, wxFrame)
71    EVT_CLOSE(EffectRack::OnClose)
72    EVT_TIMER(wxID_ANY, EffectRack::OnTimer)
73 
74    EVT_BUTTON(wxID_APPLY, EffectRack::OnApply)
75    EVT_TOGGLEBUTTON(wxID_CLEAR, EffectRack::OnBypass)
76 
77    EVT_COMMAND_RANGE(ID_REMOVE, ID_REMOVE + 99, wxEVT_COMMAND_BUTTON_CLICKED, EffectRack::OnRemove)
78    EVT_COMMAND_RANGE(ID_POWER,  ID_POWER + 99,  wxEVT_COMMAND_BUTTON_CLICKED, EffectRack::OnPower)
79    EVT_COMMAND_RANGE(ID_EDITOR, ID_EDITOR + 99, wxEVT_COMMAND_BUTTON_CLICKED, EffectRack::OnEditor)
80    EVT_COMMAND_RANGE(ID_UP,     ID_UP + 99,     wxEVT_COMMAND_BUTTON_CLICKED, EffectRack::OnUp)
81    EVT_COMMAND_RANGE(ID_DOWN,   ID_DOWN + 99,   wxEVT_COMMAND_BUTTON_CLICKED, EffectRack::OnDown)
82    EVT_COMMAND_RANGE(ID_FAV,    ID_FAV + 99,    wxEVT_COMMAND_BUTTON_CLICKED, EffectRack::OnFav)
83 END_EVENT_TABLE()
84 
85 EffectRack::EffectRack( AudacityProject &project )
86 :  wxFrame( &GetProjectFrame( project ),
87       wxID_ANY,
88       _("Effects Rack"),
89       wxDefaultPosition,
90       wxDefaultSize,
91       wxSYSTEM_MENU |
92       wxCLOSE_BOX |
93       wxCAPTION |
94       wxFRAME_NO_TASKBAR |
95       wxFRAME_FLOAT_ON_PARENT)
96 , mProject{ project }
97 {
98    mBypassing = false;
99    mNumEffects = 0;
100    mLastLatency = 0;
101    mTimer.SetOwner(this);
102 
103    mPowerPushed = CreateBitmap(power_on_16x16_xpm, false, false);
104    mPowerRaised = CreateBitmap(power_off_16x16_xpm, true, false);
105    mSettingsPushed = CreateBitmap(settings_up_16x16_xpm, false, true);
106    mSettingsRaised = CreateBitmap(settings_down_16x16_xpm, true, true);
107    mUpDisabled = CreateBitmap(up_9x16_xpm, true, true);
108    mUpPushed = CreateBitmap(up_9x16_xpm, false, true);
109    mUpRaised = CreateBitmap(up_9x16_xpm, true, true);
110    mDownDisabled = CreateBitmap(down_9x16_xpm, true, true);
111    mDownPushed = CreateBitmap(down_9x16_xpm, false, true);
112    mDownRaised = CreateBitmap(down_9x16_xpm, true, true);
113    mFavPushed = CreateBitmap(fav_down_16x16_xpm, false, false);
114    mFavRaised = CreateBitmap(fav_up_16x16_xpm, true, false);
115    mRemovePushed = CreateBitmap(remove_16x16_xpm, false, true);
116    mRemoveRaised = CreateBitmap(remove_16x16_xpm, true, true);
117 
118    {
119       auto bs = std::make_unique<wxBoxSizer>(wxVERTICAL);
120       mPanel = safenew wxPanelWrapper(this, wxID_ANY);
121       bs->Add(mPanel, 1, wxEXPAND);
122       SetSizer(bs.release());
123    }
124 
125    {
126       auto bs = std::make_unique<wxBoxSizer>(wxVERTICAL);
127       {
128          auto hs = std::make_unique<wxBoxSizer>(wxHORIZONTAL);
129          wxASSERT(mPanel); // To justify safenew
130          hs->Add(safenew wxButton(mPanel, wxID_APPLY, _("&Apply")), 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
131          hs->AddStretchSpacer();
132          mLatency = safenew wxStaticText(mPanel, wxID_ANY, _("Latency: 0"));
133          hs->Add(mLatency, 0, wxALIGN_CENTER);
134          hs->AddStretchSpacer();
135          hs->Add(safenew wxToggleButton(mPanel, wxID_CLEAR, _("&Bypass")), 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL);
136          bs->Add(hs.release(), 0, wxEXPAND);
137       }
138       bs->Add(safenew wxStaticLine(mPanel, wxID_ANY), 0, wxEXPAND);
139 
140       {
141          auto uMainSizer = std::make_unique<wxFlexGridSizer>(7);
142          uMainSizer->AddGrowableCol(6);
143          uMainSizer->SetHGap(0);
144          uMainSizer->SetVGap(0);
145          bs->Add((mMainSizer = uMainSizer.release()), 1, wxEXPAND);
146       }
147 
148       mPanel->SetSizer(bs.release());
149    }
150 
151    wxString oldPath = gPrefs->GetPath();
152    gPrefs->SetPath(wxT("/EffectsRack"));
153    size_t cnt = gPrefs->GetNumberOfEntries();
154    gPrefs->SetPath(oldPath);
155 
156    EffectManager & em = EffectManager::Get();
157    for (size_t i = 0; i < cnt; i++)
158    {
159       wxString slot;
160       gPrefs->Read(wxString::Format(wxT("/EffectsRack/Slot%02d"), i), &slot);
161 
162       Effect *effect = em.GetEffect(slot.AfterFirst(wxT(',')));
163       if (effect)
164       {
165          Add(effect, slot.BeforeFirst(wxT(',')) == wxT("1"), true);
166       }
167    }
168 
169    Fit();
170 }
171 
~EffectRack()172 EffectRack::~EffectRack()
173 {
174    gPrefs->DeleteGroup(wxT("/EffectsRack"));
175 
176    for (size_t i = 0, cnt = mEffects.size(); i < cnt; i++)
177    {
178       if (mFavState[i])
179       {
180          Effect *effect = mEffects[i];
181          gPrefs->Write(wxString::Format(wxT("/EffectsRack/Slot%02d"), i),
182                        wxString::Format(wxT("%d,%s"),
183                                         mPowerState[i],
184                                         effect->GetID()));
185       }
186    }
187 }
188 
Add(Effect * effect,bool active,bool favorite)189 void EffectRack::Add(Effect *effect, bool active, bool favorite)
190 {
191    if (mEffects.end() != std::find(mEffects.begin(), mEffects.end(), effect))
192    {
193       return;
194    }
195 
196    wxBitmapButton *bb;
197 
198    wxASSERT(mPanel); // To justify safenew
199    bb = safenew wxBitmapButton(mPanel, ID_POWER + mNumEffects, mPowerRaised);
200    bb->SetBitmapSelected(mPowerRaised);
201    bb->SetName(_("Active State"));
202    bb->SetToolTip(_("Set effect active state"));
203    mPowerState.push_back(active);
204    if (active)
205    {
206       bb->SetBitmapLabel(mPowerPushed);
207       bb->SetBitmapSelected(mPowerPushed);
208    }
209    else
210    {
211       bb->SetBitmapLabel(mPowerRaised);
212       bb->SetBitmapSelected(mPowerRaised);
213    }
214    mMainSizer->Add(bb, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
215 
216    bb = safenew wxBitmapButton(mPanel, ID_EDITOR + mNumEffects, mSettingsRaised);
217    bb->SetBitmapSelected(mSettingsPushed);
218    bb->SetName(_("Show/Hide Editor"));
219    bb->SetToolTip(_("Open/close effect editor"));
220    mMainSizer->Add(bb, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
221 
222    bb = safenew wxBitmapButton(mPanel, ID_UP + mNumEffects, mUpRaised);
223    bb->SetBitmapSelected(mUpPushed);
224    bb->SetBitmapDisabled(mUpDisabled);
225    bb->SetName(_("Move Up"));
226    bb->SetToolTip(_("Move effect up in the rack"));
227    mMainSizer->Add(bb, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
228 
229    bb = safenew wxBitmapButton(mPanel, ID_DOWN + mNumEffects, mDownRaised);
230    bb->SetBitmapSelected(mDownPushed);
231    bb->SetBitmapDisabled(mDownDisabled);
232    bb->SetName(_("Move Down"));
233    bb->SetToolTip(_("Move effect down in the rack"));
234    mMainSizer->Add(bb, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
235 
236    bb = safenew wxBitmapButton(mPanel, ID_FAV + mNumEffects, mFavRaised);
237    bb->SetBitmapSelected(mFavPushed);
238    bb->SetName(_("Favorite"));
239    bb->SetToolTip(_("Mark effect as a favorite"));
240    mFavState.push_back(favorite);
241    if (favorite)
242    {
243       bb->SetBitmapLabel(mFavPushed);
244       bb->SetBitmapSelected(mFavPushed);
245    }
246    else
247    {
248       bb->SetBitmapLabel(mFavRaised);
249       bb->SetBitmapSelected(mFavRaised);
250    }
251    mMainSizer->Add(bb, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
252 
253    bb = safenew wxBitmapButton(mPanel, ID_REMOVE + mNumEffects, mRemoveRaised);
254    bb->SetBitmapSelected(mRemovePushed);
255    bb->SetName(_("Remove"));
256    bb->SetToolTip(_("Remove effect from the rack"));
257    mMainSizer->Add(bb, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
258 
259    wxStaticText *text = safenew wxStaticText(mPanel, ID_NAME + mNumEffects,
260       effect->GetName().Translation() );
261    text->SetToolTip(_("Name of the effect"));
262    mMainSizer->Add(text, 0, wxEXPAND | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL, 5);
263 
264    mMainSizer->Layout();
265    SetSize(GetMinSize());
266    Fit();
267    Update();
268 
269    mEffects.push_back(effect);
270    mNumEffects++;
271 
272    if (!mTimer.IsRunning())
273    {
274       mTimer.Start(1000);
275    }
276 
277    if (active)
278    {
279       UpdateActive();
280    }
281 }
282 
OnClose(wxCloseEvent & evt)283 void EffectRack::OnClose(wxCloseEvent & evt)
284 {
285    Show(false);
286    evt.Veto();
287 }
288 
OnTimer(wxTimerEvent & WXUNUSED (evt))289 void EffectRack::OnTimer(wxTimerEvent & WXUNUSED(evt))
290 {
291    int latency = RealtimeEffectManager::Get().GetRealtimeLatency();
292    if (latency != mLastLatency)
293    {
294       mLatency->SetLabel(wxString::Format(_("Latency: %4d"), latency));
295       mLatency->Refresh();
296       mLastLatency = latency;
297    }
298 }
299 
OnApply(wxCommandEvent & WXUNUSED (evt))300 void EffectRack::OnApply(wxCommandEvent & WXUNUSED(evt))
301 {
302    AudacityProject *project = &mProject;
303 
304    bool success = false;
305    auto state = UndoManager::Get( *project ).GetCurrentState();
306    auto cleanup = finally( [&] {
307       if(!success)
308          // This is like a rollback of state
309          ProjectHistory::Get( *project ).SetStateTo( state, false );
310    } );
311 
312    for (size_t i = 0, cnt = mEffects.size(); i < cnt; i++)
313    {
314       if (mPowerState[i])
315       {
316          if (!EffectUI::DoEffect(mEffects[i]->GetID(),
317                            *project,
318                            EffectManager::kConfigured))
319             // If any effect fails (or throws), then stop.
320             return;
321       }
322    }
323 
324    success = true;
325 
326    // Only after all succeed, do the following.
327    for (size_t i = 0, cnt = mEffects.size(); i < cnt; i++)
328    {
329       if (mPowerState[i])
330       {
331          mPowerState[i] = false;
332 
333          wxBitmapButton *btn =
334             static_cast<wxBitmapButton *>(FindWindowById(ID_POWER + i));
335          btn->SetBitmapLabel(mPowerRaised);
336          btn->SetBitmapSelected(mPowerRaised);
337       }
338    }
339 
340    UpdateActive();
341 }
342 
OnBypass(wxCommandEvent & evt)343 void EffectRack::OnBypass(wxCommandEvent & evt)
344 {
345    mBypassing = evt.GetInt() != 0;
346    UpdateActive();
347 }
348 
OnPower(wxCommandEvent & evt)349 void EffectRack::OnPower(wxCommandEvent & evt)
350 {
351    wxBitmapButton *btn =  static_cast<wxBitmapButton *>(evt.GetEventObject());
352 
353    int index = GetEffectIndex(btn);
354    mPowerState[index] = !mPowerState[index];
355    if (mPowerState[index])
356    {
357       btn->SetBitmapLabel(mPowerPushed);
358       btn->SetBitmapSelected(mPowerPushed);
359    }
360    else
361    {
362       btn->SetBitmapLabel(mPowerRaised);
363       btn->SetBitmapSelected(mPowerRaised);
364    }
365 
366    UpdateActive();
367 }
368 
OnEditor(wxCommandEvent & evt)369 void EffectRack::OnEditor(wxCommandEvent & evt)
370 {
371    wxBitmapButton *btn =  static_cast<wxBitmapButton *>(evt.GetEventObject());
372 
373    evt.Skip();
374 
375    int index = GetEffectIndex(btn);
376    if (index < 0)
377    {
378       return;
379    }
380 
381    auto pEffect = mEffects[index];
382    pEffect->ShowInterface( *GetParent(), EffectUI::DialogFactory,
383       pEffect->IsBatchProcessing() );
384 }
385 
OnUp(wxCommandEvent & evt)386 void EffectRack::OnUp(wxCommandEvent & evt)
387 {
388    wxBitmapButton *btn =  static_cast<wxBitmapButton *>(evt.GetEventObject());
389 
390    evt.Skip();
391 
392    int index = GetEffectIndex(btn);
393    if (index <= 0)
394    {
395       return;
396    }
397 
398    MoveRowUp(index);
399 }
400 
OnDown(wxCommandEvent & evt)401 void EffectRack::OnDown(wxCommandEvent & evt)
402 {
403    wxBitmapButton *btn =  static_cast<wxBitmapButton *>(evt.GetEventObject());
404 
405    evt.Skip();
406 
407    int index = GetEffectIndex(btn);
408    if (index < 0 || index == (mMainSizer->GetChildren().GetCount() / NUMCOLS) - 1)
409    {
410       return;
411    }
412 
413    MoveRowUp(index + 1);
414 }
415 
OnFav(wxCommandEvent & evt)416 void EffectRack::OnFav(wxCommandEvent & evt)
417 {
418    wxBitmapButton *btn =  static_cast<wxBitmapButton *>(evt.GetEventObject());
419 
420    int index = GetEffectIndex(btn);
421    mFavState[index] = !mFavState[index];
422    if (mFavState[index])
423    {
424       btn->SetBitmapLabel(mFavPushed);
425       btn->SetBitmapSelected(mFavPushed);
426    }
427    else
428    {
429       btn->SetBitmapLabel(mFavRaised);
430       btn->SetBitmapSelected(mFavRaised);
431    }
432 }
433 
OnRemove(wxCommandEvent & evt)434 void EffectRack::OnRemove(wxCommandEvent & evt)
435 {
436    wxBitmapButton *btn =  static_cast<wxBitmapButton *>(evt.GetEventObject());
437 
438    evt.Skip();
439 
440    int index = GetEffectIndex(btn);
441    if (index < 0)
442    {
443       return;
444    }
445 
446    mEffects.erase(mEffects.begin() + index);
447    mPowerState.erase(mPowerState.begin() + index);
448    mFavState.erase(mFavState.begin() + index);
449 
450    if (mEffects.size() == 0)
451    {
452       if (mTimer.IsRunning())
453       {
454          mTimer.Stop();
455       }
456    }
457 
458    index *= NUMCOLS;
459 
460    for (int i = 0; i < NUMCOLS; i++)
461    {
462       std::unique_ptr<wxWindow> w {mMainSizer->GetItem(index)->GetWindow()};
463       mMainSizer->Detach(index);
464    }
465 
466    mMainSizer->Layout();
467    Fit();
468 
469    UpdateActive();
470 }
471 
CreateBitmap(const char * const xpm[],bool up,bool pusher)472 wxBitmap EffectRack::CreateBitmap(const char *const xpm[], bool up, bool pusher)
473 {
474    wxMemoryDC dc;
475    wxBitmap pic(xpm);
476 
477    wxBitmap mod(pic.GetWidth() + 6, pic.GetHeight() + 6);
478    dc.SelectObject(mod);
479 #if defined( __WXGTK__ )
480    wxColour newColour = wxSystemSettings::GetColour( wxSYS_COLOUR_BACKGROUND );
481 #else
482    wxColour newColour = wxSystemSettings::GetColour( wxSYS_COLOUR_3DFACE );
483 #endif
484    dc.SetBackground(wxBrush(newColour));
485    dc.Clear();
486 
487    int offset = 3;
488    if (pusher)
489    {
490       if (!up)
491       {
492          offset += 1;
493       }
494    }
495    dc.DrawBitmap(pic, offset, offset, true);
496 
497    dc.SelectObject(wxNullBitmap);
498 
499    return mod;
500 }
501 
GetEffectIndex(wxWindow * win)502 int EffectRack::GetEffectIndex(wxWindow *win)
503 {
504    int col = (win->GetId() - ID_BASE) / ID_RANGE;
505    int row;
506    int cnt = mMainSizer->GetChildren().GetCount() / NUMCOLS;
507    for (row = 0; row < cnt; row++)
508    {
509       wxSizerItem *si = mMainSizer->GetItem((row * NUMCOLS) + col);
510       if (si->GetWindow() == win)
511       {
512          break;
513       }
514    }
515 
516    if (row == cnt)
517    {
518       return -1;
519    }
520 
521    return row;
522 }
523 
MoveRowUp(int row)524 void EffectRack::MoveRowUp(int row)
525 {
526    Effect *effect = mEffects[row];
527    mEffects.erase(mEffects.begin() + row);
528    mEffects.insert(mEffects.begin() + row - 1, effect);
529 
530    int state = mPowerState[row];
531    mPowerState.erase(mPowerState.begin() + row);
532    mPowerState.insert(mPowerState.begin() + row - 1, state);
533 
534    state = mFavState[row];
535    mFavState.erase(mFavState.begin() + row);
536    mFavState.insert(mFavState.begin() + row - 1, state);
537 
538    row *= NUMCOLS;
539 
540    for (int i = 0; i < NUMCOLS; i++)
541    {
542       wxSizerItem *si = mMainSizer->GetItem(row + NUMCOLS - 1);
543       wxWindow *w = si->GetWindow();
544       int flags = si->GetFlag();
545       int border = si->GetBorder();
546       int prop = si->GetProportion();
547       mMainSizer->Detach(row + NUMCOLS - 1);
548       mMainSizer->Insert(row - NUMCOLS, w, prop, flags, border);
549    }
550 
551    mMainSizer->Layout();
552    Refresh();
553 
554    UpdateActive();
555 }
556 
UpdateActive()557 void EffectRack::UpdateActive()
558 {
559    mActive.clear();
560 
561    if (!mBypassing)
562    {
563       for (size_t i = 0, cnt = mEffects.size(); i < cnt; i++)
564       {
565          if (mPowerState[i])
566          {
567             mActive.push_back(mEffects[i]);
568          }
569       }
570    }
571 
572    RealtimeEffectManager::Get().RealtimeSetEffects(
573       { mActive.begin(), mActive.end() }
574    );
575 }
576 
577 namespace
578 {
579 AudacityProject::AttachedWindows::RegisteredFactory sKey{
__anone5c31cd40302( ) 580    []( AudacityProject &parent ) -> wxWeakRef< wxWindow > {
581       auto result = safenew EffectRack( parent );
582       result->CenterOnParent();
583       return result;
584    }
585 };
586 }
587 
Get(AudacityProject & project)588 EffectRack &EffectRack::Get( AudacityProject &project )
589 {
590    return project.AttachedWindows::Get< EffectRack >( sKey );
591 }
592 
593 #endif
594 
595 ///////////////////////////////////////////////////////////////////////////////
596 //
597 // EffectPanel
598 //
599 ///////////////////////////////////////////////////////////////////////////////
600 
601 class EffectPanel final : public wxPanelWrapper
602 {
603 public:
EffectPanel(wxWindow * parent)604    EffectPanel(wxWindow *parent)
605    :  wxPanelWrapper(parent)
606    {
607       // This fools NVDA into not saying "Panel" when the dialog gets focus
608       SetName(TranslatableString::Inaudible);
609       SetLabel(TranslatableString::Inaudible);
610 
611       mAcceptsFocus = true;
612    }
613 
~EffectPanel()614    virtual ~EffectPanel()
615    {
616    }
617 
618    // ============================================================================
619    // wxWindow implementation
620    // ============================================================================
621 
AcceptsFocus() const622    bool AcceptsFocus() const override
623    {
624       return mAcceptsFocus;
625    }
626 
627    // So that wxPanel is not included in Tab traversal, when required - see wxWidgets bug 15581
AcceptsFocusFromKeyboard() const628    bool AcceptsFocusFromKeyboard() const override
629    {
630       return mAcceptsFocus;
631    }
632 
633    // ============================================================================
634    // EffectPanel implementation
635    // ============================================================================
SetAccept(bool accept)636    void SetAccept(bool accept)
637    {
638       mAcceptsFocus = accept;
639    }
640 
641 private:
642    bool mAcceptsFocus;
643 };
644 
645 ///////////////////////////////////////////////////////////////////////////////
646 //
647 // EffectUIHost
648 //
649 ///////////////////////////////////////////////////////////////////////////////
650 
651 #include "../../images/Effect.h"
652 #include "../AudioIO.h"
653 #include "../CommonCommandFlags.h"
654 #include "../Menus.h"
655 #include "../prefs/GUISettings.h" // for RTL_WORKAROUND
656 #include "Project.h"
657 #include "../ProjectAudioManager.h"
658 #include "../ShuttleGui.h"
659 #include "ViewInfo.h"
660 #include "../commands/AudacityCommand.h"
661 #include "../commands/CommandContext.h"
662 #include "../widgets/AudacityMessageBox.h"
663 #include "../widgets/HelpSystem.h"
664 
665 #include <wx/app.h>
666 #include <wx/bmpbuttn.h>
667 #include <wx/checkbox.h>
668 #include <wx/dcclient.h>
669 #include <wx/dcmemory.h>
670 #include <wx/menu.h>
671 #include <wx/settings.h>
672 #include <wx/sizer.h>
673 #include <wx/textctrl.h>
674 
675 #if defined(__WXMAC__)
676 #include <Cocoa/Cocoa.h>
677 #endif
678 
679 static const int kDummyID = 20000;
680 static const int kSaveAsID = 20001;
681 static const int kImportID = 20002;
682 static const int kExportID = 20003;
683 static const int kDefaultsID = 20004;
684 static const int kOptionsID = 20005;
685 static const int kUserPresetsDummyID = 20006;
686 static const int kDeletePresetDummyID = 20007;
687 static const int kMenuID = 20100;
688 static const int kEnableID = 20101;
689 static const int kPlayID = 20102;
690 static const int kRewindID = 20103;
691 static const int kFFwdID = 20104;
692 static const int kPlaybackID = 20105;
693 static const int kCaptureID = 20106;
694 static const int kUserPresetsID = 21000;
695 static const int kDeletePresetID = 22000;
696 static const int kFactoryPresetsID = 23000;
697 
BEGIN_EVENT_TABLE(EffectUIHost,wxDialogWrapper)698 BEGIN_EVENT_TABLE(EffectUIHost, wxDialogWrapper)
699 EVT_INIT_DIALOG(EffectUIHost::OnInitDialog)
700 EVT_ERASE_BACKGROUND(EffectUIHost::OnErase)
701 EVT_PAINT(EffectUIHost::OnPaint)
702 EVT_CLOSE(EffectUIHost::OnClose)
703 EVT_BUTTON(wxID_APPLY, EffectUIHost::OnApply)
704 EVT_BUTTON(wxID_CANCEL, EffectUIHost::OnCancel)
705 EVT_BUTTON(wxID_HELP, EffectUIHost::OnHelp)
706 EVT_BUTTON(eDebugID, EffectUIHost::OnDebug)
707 EVT_BUTTON(kMenuID, EffectUIHost::OnMenu)
708 EVT_CHECKBOX(kEnableID, EffectUIHost::OnEnable)
709 EVT_BUTTON(kPlayID, EffectUIHost::OnPlay)
710 EVT_BUTTON(kRewindID, EffectUIHost::OnRewind)
711 EVT_BUTTON(kFFwdID, EffectUIHost::OnFFwd)
712 EVT_MENU(kSaveAsID, EffectUIHost::OnSaveAs)
713 EVT_MENU(kImportID, EffectUIHost::OnImport)
714 EVT_MENU(kExportID, EffectUIHost::OnExport)
715 EVT_MENU(kOptionsID, EffectUIHost::OnOptions)
716 EVT_MENU(kDefaultsID, EffectUIHost::OnDefaults)
717 EVT_MENU_RANGE(kUserPresetsID, kUserPresetsID + 999, EffectUIHost::OnUserPreset)
718 EVT_MENU_RANGE(kDeletePresetID, kDeletePresetID + 999, EffectUIHost::OnDeletePreset)
719 EVT_MENU_RANGE(kFactoryPresetsID, kFactoryPresetsID + 999, EffectUIHost::OnFactoryPreset)
720 END_EVENT_TABLE()
721 
722 EffectUIHost::EffectUIHost(wxWindow *parent,
723                            AudacityProject &project,
724                            Effect *effect,
725                            EffectUIClientInterface *client)
726 :  wxDialogWrapper(parent, wxID_ANY, effect->GetName(),
727                    wxDefaultPosition, wxDefaultSize,
728                    wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMINIMIZE_BOX | wxMAXIMIZE_BOX)
729 {
730 #if defined(__WXMAC__)
731    // Make sure the effect window actually floats above the main window
732    [ [((NSView *)GetHandle()) window] setLevel:NSFloatingWindowLevel];
733 #endif
734 
735    SetName( effect->GetName() );
736    SetExtraStyle(GetExtraStyle() | wxWS_EX_VALIDATE_RECURSIVELY);
737 
738    mParent = parent;
739    mEffect = effect;
740    mCommand = NULL;
741    mClient = client;
742 
743    mProject = &project;
744 
745    mInitialized = false;
746    mSupportsRealtime = false;
747 
748    mDisableTransport = false;
749 
750    mEnabled = true;
751 
752    mPlayPos = 0.0;
753    mClient->SetHostUI(this);
754 }
755 
EffectUIHost(wxWindow * parent,AudacityProject & project,AudacityCommand * command,EffectUIClientInterface * client)756 EffectUIHost::EffectUIHost(wxWindow *parent,
757                            AudacityProject &project,
758                            AudacityCommand *command,
759                            EffectUIClientInterface *client)
760 :  wxDialogWrapper(parent, wxID_ANY, XO("Some Command") /*command->GetName()*/,
761                    wxDefaultPosition, wxDefaultSize,
762                    wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMINIMIZE_BOX | wxMAXIMIZE_BOX)
763 {
764 #if defined(__WXMAC__)
765    // Make sure the effect window actually floats above the main window
766    [ [((NSView *)GetHandle()) window] setLevel:NSFloatingWindowLevel];
767 #endif
768 
769    //SetName( command->GetName() );
770    SetExtraStyle(wxWS_EX_VALIDATE_RECURSIVELY);
771 
772    mParent = parent;
773    mEffect = NULL;
774    mCommand = command;
775    mClient = client;
776 
777    mProject = &project;
778 
779    mInitialized = false;
780    mSupportsRealtime = false;
781 
782    mDisableTransport = false;
783 
784    mEnabled = true;
785 
786    mPlayPos = 0.0;
787    mClient->SetHostUI(this);
788 }
789 
790 
791 
792 
~EffectUIHost()793 EffectUIHost::~EffectUIHost()
794 {
795    CleanupRealtime();
796 
797    if (mClient)
798    {
799       if (mNeedsResume)
800          Resume();
801 
802       mClient->CloseUI();
803       mClient = NULL;
804    }
805 }
806 
807 // ============================================================================
808 // wxWindow implementation
809 // ============================================================================
810 
TransferDataToWindow()811 bool EffectUIHost::TransferDataToWindow()
812 {
813    if( mEffect )
814       return mEffect->TransferDataToWindow();
815    if( mCommand )
816       return mCommand->TransferDataToWindow();
817    return false;
818 }
819 
TransferDataFromWindow()820 bool EffectUIHost::TransferDataFromWindow()
821 {
822    if( mEffect)
823       return mEffect->TransferDataFromWindow();
824    if( mCommand)
825       return mCommand->TransferDataFromWindow();
826    return false;
827 }
828 
829 // ============================================================================
830 // wxDialog implementation
831 // ============================================================================
832 
ShowModal()833 int EffectUIHost::ShowModal()
834 {
835 #if defined(__WXMSW__)
836    // Swap the Close and Apply buttons
837    wxSizer *sz = mApplyBtn->GetContainingSizer();
838    wxASSERT(mApplyBtn->GetParent()); // To justify safenew
839    wxButton *apply = safenew wxButton(mApplyBtn->GetParent(), wxID_APPLY);
840    sz->Replace(mCloseBtn, apply);
841    sz->Replace(mApplyBtn, mCloseBtn);
842    sz->Layout();
843    mApplyBtn->Destroy();
844    mApplyBtn = apply;
845    mApplyBtn->SetDefault();
846    mApplyBtn->SetLabel(wxGetStockLabel(wxID_OK, 0));
847    mCloseBtn->SetLabel(wxGetStockLabel(wxID_CANCEL, 0));
848 #else
849    mApplyBtn->SetLabel(wxGetStockLabel(wxID_OK));
850    mCloseBtn->SetLabel(wxGetStockLabel(wxID_CANCEL));
851 #endif
852 
853    Layout();
854 
855    return wxDialogWrapper::ShowModal();
856 }
857 
858 // ============================================================================
859 // EffectUIHost implementation
860 // ============================================================================
861 
BuildButtonBar(wxWindow * parent)862 wxPanel *EffectUIHost::BuildButtonBar(wxWindow *parent)
863 {
864    mSupportsRealtime = mEffect && mEffect->SupportsRealtime();
865    mIsGUI = mClient->IsGraphicalUI();
866    mIsBatch = (mEffect && mEffect->IsBatchProcessing()) ||
867       (mCommand && mCommand->IsBatchProcessing());
868 
869    int margin = 0;
870 #if defined(__WXMAC__)
871    margin = 3; // I'm sure it's needed because of the order things are created...
872 #endif
873 
874    const auto bar = safenew wxPanelWrapper(parent, wxID_ANY);
875 
876    // This fools NVDA into not saying "Panel" when the dialog gets focus
877    bar->SetName(TranslatableString::Inaudible);
878    bar->SetLabel(TranslatableString::Inaudible);
879 
880    ShuttleGui S{ bar, eIsCreating,
881       false /* horizontal */,
882       { -1, -1 } /* minimum size */
883    };
884    {
885       S.SetBorder( margin );
886 
887       if (!mIsGUI)
888       {
889          mMenuBtn = S.Id( kMenuID )
890             .ToolTip(XO("Manage presets and options"))
891             .AddButton( XXO("&Manage"), wxALIGN_CENTER | wxTOP | wxBOTTOM );
892       }
893       else
894       {
895          mMenuBtn = S.Id( kMenuID )
896             .ToolTip(XO("Manage presets and options"))
897             .Name(XO("&Manage"))
898             .AddBitmapButton( CreateBitmap(effect_menu_xpm, true, true) );
899          mMenuBtn->SetBitmapPressed(CreateBitmap(effect_menu_xpm, false, true));
900       }
901 
902       S.AddSpace( 5, 5 );
903 
904       if (!mIsBatch)
905       {
906          if (!mIsGUI)
907          {
908             if (mSupportsRealtime)
909             {
910                mPlayToggleBtn = S.Id( kPlayID )
911                   .ToolTip(XO("Start and stop playback"))
912                   .AddButton( XXO("Start &Playback"),
913                               wxALIGN_CENTER | wxTOP | wxBOTTOM );
914             }
915             else if (mEffect &&
916                (mEffect->GetType() != EffectTypeAnalyze) &&
917                (mEffect->GetType() != EffectTypeTool) )
918             {
919                mPlayToggleBtn = S.Id( kPlayID )
920                   .ToolTip(XO("Preview effect"))
921                   .AddButton( XXO("&Preview"),
922                               wxALIGN_CENTER | wxTOP | wxBOTTOM );
923             }
924          }
925          else
926          {
927             mPlayBM = CreateBitmap(effect_play_xpm, true, false);
928             mPlayDisabledBM = CreateBitmap(effect_play_disabled_xpm, true, true);
929             mStopBM = CreateBitmap(effect_stop_xpm, true, false);
930             mStopDisabledBM = CreateBitmap(effect_stop_disabled_xpm, true, false);
931             mPlayBtn = S.Id( kPlayID ).AddBitmapButton( mPlayBM );
932             mPlayBtn->SetBitmapDisabled(mPlayDisabledBM);
933             mPlayBtn->SetBitmapPressed(CreateBitmap(effect_play_xpm, false, true));
934             if (!mSupportsRealtime)
935             {
936                mPlayBtn->SetToolTip(_("Preview effect"));
937 #if defined(__WXMAC__)
938                mPlayBtn->SetName(_("Preview effect"));
939 #else
940                mPlayBtn->SetLabel(_("&Preview effect"));
941 #endif
942             }
943          }
944 
945          if (mSupportsRealtime)
946          {
947             if (!mIsGUI)
948             {
949                mRewindBtn = S.Id( kRewindID )
950                   .ToolTip(XO("Skip backward"))
951                   .AddButton( XXO("Skip &Backward"),
952                               wxALIGN_CENTER | wxTOP | wxBOTTOM );
953             }
954             else
955             {
956                mRewindBtn = S.Id( kRewindID )
957                   .ToolTip(XO("Skip backward"))
958                   .Name(XO("Skip &Backward"))
959                   .AddBitmapButton( CreateBitmap(
960                      effect_rewind_xpm, true, true) );
961                mRewindBtn->SetBitmapDisabled(
962                      CreateBitmap(effect_rewind_disabled_xpm, true, false));
963                mRewindBtn->SetBitmapPressed(CreateBitmap(effect_rewind_xpm, false, true));
964             }
965 
966             if (!mIsGUI)
967             {
968                mFFwdBtn = S.Id( kFFwdID )
969                   .ToolTip(XO("Skip forward"))
970                   .AddButton( XXO("Skip &Forward"),
971                      wxALIGN_CENTER | wxTOP | wxBOTTOM );
972             }
973             else
974             {
975                mFFwdBtn = S.Id( kFFwdID )
976                   .ToolTip(XO("Skip forward"))
977                   .Name(XO("Skip &Forward"))
978                   .AddBitmapButton( CreateBitmap(
979                      effect_ffwd_xpm, true, true) );
980                mFFwdBtn->SetBitmapDisabled(
981                   CreateBitmap(effect_ffwd_disabled_xpm, true, false));
982                mFFwdBtn->SetBitmapPressed(CreateBitmap(effect_ffwd_xpm, false, true));
983             }
984 
985             S.AddSpace( 5, 5 );
986 
987             mEnableCb = S.Id( kEnableID )
988                .Position(wxALIGN_CENTER | wxTOP | wxBOTTOM)
989                .Name(XO("Enable"))
990                .AddCheckBox( XXO("&Enable"), mEnabled );
991             //
992          }
993       }
994    }
995 
996    bar->GetSizer()->SetSizeHints( bar );
997 
998    return bar;
999 }
1000 
Initialize()1001 bool EffectUIHost::Initialize()
1002 {
1003    {
1004       auto gAudioIO = AudioIO::Get();
1005       mDisableTransport = !gAudioIO->IsAvailable(*mProject);
1006       mPlaying = gAudioIO->IsStreamActive(); // not exactly right, but will suffice
1007       mCapturing = gAudioIO->IsStreamActive() && gAudioIO->GetNumCaptureChannels() > 0 && !gAudioIO->IsMonitoring();
1008    }
1009 
1010    EffectPanel *w {};
1011    ShuttleGui S{ this, eIsCreating };
1012    {
1013       S.StartHorizontalLay( wxEXPAND );
1014       {
1015          Destroy_ptr<EffectPanel> uw{ safenew EffectPanel( S.GetParent() ) };
1016          RTL_WORKAROUND(uw.get());
1017 
1018          // Try to give the window a sensible default/minimum size
1019          uw->SetMinSize(wxSize(wxMax(600, mParent->GetSize().GetWidth() * 2 / 3),
1020             mParent->GetSize().GetHeight() / 2));
1021 
1022          ShuttleGui S1{ uw.get(), eIsCreating };
1023          if (!mClient->PopulateUI(S1))
1024          {
1025             return false;
1026          }
1027 
1028          S.Prop( 1 )
1029             .Position(wxEXPAND)
1030             .AddWindow((w = uw.release()));
1031       }
1032       S.EndHorizontalLay();
1033 
1034       S.StartPanel();
1035       {
1036          const auto bar = BuildButtonBar( S.GetParent() );
1037 
1038          long buttons;
1039          if ( mEffect && mEffect->ManualPage().empty() && mEffect->HelpPage().empty()) {
1040             buttons = eApplyButton | eCloseButton;
1041             this->SetAcceleratorTable(wxNullAcceleratorTable);
1042          }
1043          else {
1044             buttons = eApplyButton | eCloseButton | eHelpButton;
1045             wxAcceleratorEntry entries[1];
1046 #if defined(__WXMAC__)
1047             // Is there a standard shortcut on Mac?
1048 #else
1049             entries[0].Set(wxACCEL_NORMAL, (int) WXK_F1, wxID_HELP);
1050 #endif
1051             wxAcceleratorTable accel(1, entries);
1052             this->SetAcceleratorTable(accel);
1053          }
1054 
1055          if (mEffect && mEffect->mUIDebug) {
1056             buttons |= eDebugButton;
1057          }
1058 
1059          S.AddStandardButtons(buttons, bar);
1060       }
1061       S.EndPanel();
1062    }
1063 
1064    Layout();
1065    Fit();
1066    Center();
1067 
1068    mApplyBtn = (wxButton *) FindWindow(wxID_APPLY);
1069    mCloseBtn = (wxButton *) FindWindow(wxID_CANCEL);
1070 
1071    UpdateControls();
1072 
1073    w->SetAccept(!mIsGUI);
1074    (!mIsGUI ? w : FindWindow(wxID_APPLY))->SetFocus();
1075 
1076    LoadUserPresets();
1077 
1078    InitializeRealtime();
1079 
1080    SetMinSize(GetSize());
1081    return true;
1082 }
1083 
OnInitDialog(wxInitDialogEvent & evt)1084 void EffectUIHost::OnInitDialog(wxInitDialogEvent & evt)
1085 {
1086    // Do default handling
1087    wxDialogWrapper::OnInitDialog(evt);
1088 
1089 #if wxCHECK_VERSION(3, 0, 0)
1090    //#warning "check to see if this still needed in wx3"
1091 #endif
1092 
1093    // Pure hackage coming down the pike...
1094    //
1095    // I have no idea why, but if a wxTextCtrl is the first control in the
1096    // panel, then its contents will not be automatically selected when the
1097    // dialog is displayed.
1098    //
1099    // So, we do the selection manually.
1100    wxTextCtrl *focused = wxDynamicCast(FindFocus(), wxTextCtrl);
1101    if (focused)
1102    {
1103       focused->SelectAll();
1104    }
1105 }
1106 
OnErase(wxEraseEvent & WXUNUSED (evt))1107 void EffectUIHost::OnErase(wxEraseEvent & WXUNUSED(evt))
1108 {
1109    // Ignore it
1110 }
1111 
OnPaint(wxPaintEvent & WXUNUSED (evt))1112 void EffectUIHost::OnPaint(wxPaintEvent & WXUNUSED(evt))
1113 {
1114    wxPaintDC dc(this);
1115 
1116    dc.Clear();
1117 }
1118 
OnClose(wxCloseEvent & WXUNUSED (evt))1119 void EffectUIHost::OnClose(wxCloseEvent & WXUNUSED(evt))
1120 {
1121    DoCancel();
1122 
1123    CleanupRealtime();
1124 
1125    Hide();
1126 
1127    if (mNeedsResume)
1128       Resume();
1129    mClient->CloseUI();
1130    mClient = NULL;
1131 
1132    Destroy();
1133 }
1134 
OnApply(wxCommandEvent & evt)1135 void EffectUIHost::OnApply(wxCommandEvent & evt)
1136 {
1137    auto &project = *mProject;
1138 
1139    // On wxGTK (wx2.8.12), the default action is still executed even if
1140    // the button is disabled.  This appears to affect all wxDialogs, not
1141    // just our Effects dialogs.  So, this is a only temporary workaround
1142    // for legacy effects that disable the OK button.  Hopefully this has
1143    // been corrected in wx3.
1144    if (!FindWindow(wxID_APPLY)->IsEnabled())
1145    {
1146       return;
1147    }
1148 
1149    // Honor the "select all if none" preference...a little hackish, but whatcha gonna do...
1150    if (!mIsBatch &&
1151        mEffect &&
1152        mEffect->GetType() != EffectTypeGenerate &&
1153        mEffect->GetType() != EffectTypeTool &&
1154        ViewInfo::Get( project ).selectedRegion.isPoint())
1155    {
1156       auto flags = AlwaysEnabledFlag;
1157       bool allowed =
1158       MenuManager::Get( project ).ReportIfActionNotAllowed(
1159          mEffect->GetName(),
1160          flags,
1161          WaveTracksSelectedFlag() | TimeSelectedFlag());
1162       if (!allowed)
1163          return;
1164    }
1165 
1166    if (!mClient->ValidateUI())
1167    {
1168       return;
1169    }
1170 
1171    // This will take care of calling TransferDataFromWindow() for an effect.
1172    if (mEffect &&  !mEffect->SaveUserPreset(mEffect->GetCurrentSettingsGroup()))
1173    {
1174       return;
1175    }
1176    // This will take care of calling TransferDataFromWindow() for a command.
1177    if (mCommand ){
1178       wxString params;
1179       mCommand->GetAutomationParameters( params );
1180    }
1181 
1182    if( mEffect )
1183       mEffect->mUIResultID = evt.GetId();
1184 
1185    if (IsModal())
1186    {
1187       mDismissed = true;
1188 
1189       EndModal(true);
1190 
1191       Close();
1192 
1193       return;
1194    }
1195 
1196    // Progress dialog no longer yields, so this "shouldn't" be necessary (yet to be proven
1197    // for sure), but it is a nice visual cue that something is going on.
1198    mApplyBtn->Disable();
1199    auto cleanup = finally( [&] { mApplyBtn->Enable(); } );
1200 
1201    if( mEffect ) {
1202       CommandContext context( project );
1203       // This is absolute hackage...but easy and I can't think of another way just now.
1204       //
1205       // It should callback to the EffectManager to kick off the processing
1206       EffectUI::DoEffect(mEffect->GetID(), context,
1207          EffectManager::kConfigured);
1208    }
1209 
1210    if( mCommand )
1211       // PRL:  I don't like the global and would rather pass *mProject!
1212       // But I am preserving old behavior
1213       mCommand->Apply( CommandContext{ project } );
1214 }
1215 
DoCancel()1216 void EffectUIHost::DoCancel()
1217 {
1218    if (!mDismissed) {
1219       if( mEffect )
1220          mEffect->mUIResultID = wxID_CANCEL;
1221 
1222       if (IsModal())
1223          EndModal(false);
1224       else
1225          Hide();
1226 
1227       mDismissed = true;
1228    }
1229 }
1230 
OnCancel(wxCommandEvent & WXUNUSED (evt))1231 void EffectUIHost::OnCancel(wxCommandEvent & WXUNUSED(evt))
1232 {
1233    DoCancel();
1234    Close();
1235 }
1236 
OnHelp(wxCommandEvent & WXUNUSED (event))1237 void EffectUIHost::OnHelp(wxCommandEvent & WXUNUSED(event))
1238 {
1239    if (mEffect && mEffect->GetFamily() == NYQUISTEFFECTS_FAMILY && (mEffect->ManualPage().empty())) {
1240       // Old ShowHelp required when there is no on-line manual.
1241       // Always use default web browser to allow full-featured HTML pages.
1242       HelpSystem::ShowHelp(FindWindow(wxID_HELP), mEffect->HelpPage(), wxEmptyString, true, true);
1243    }
1244    else if( mEffect )
1245    {
1246       // otherwise use the NEW ShowHelp
1247       HelpSystem::ShowHelp(FindWindow(wxID_HELP), mEffect->ManualPage(), true);
1248    }
1249 }
1250 
OnDebug(wxCommandEvent & evt)1251 void EffectUIHost::OnDebug(wxCommandEvent & evt)
1252 {
1253    OnApply(evt);
1254    if( mEffect )
1255       mEffect->mUIResultID = evt.GetId();
1256 }
1257 
OnMenu(wxCommandEvent & WXUNUSED (evt))1258 void EffectUIHost::OnMenu(wxCommandEvent & WXUNUSED(evt))
1259 {
1260    wxMenu menu;
1261    menu.Bind(wxEVT_MENU, [](auto&){}, kUserPresetsDummyID);
1262    menu.Bind(wxEVT_MENU, [](auto&){}, kDeletePresetDummyID);
1263    if( !mEffect )
1264       return;
1265 
1266    LoadUserPresets();
1267 
1268    if (mUserPresets.size() == 0)
1269    {
1270       menu.Append(kUserPresetsDummyID, _("User Presets"))->Enable(false);
1271    }
1272    else
1273    {
1274       auto sub = std::make_unique<wxMenu>();
1275       for (size_t i = 0, cnt = mUserPresets.size(); i < cnt; i++)
1276       {
1277          sub->Append(kUserPresetsID + i, mUserPresets[i]);
1278       }
1279       menu.Append(0, _("User Presets"), sub.release());
1280    }
1281 
1282    menu.Append(kSaveAsID, _("Save Preset..."));
1283 
1284    if (mUserPresets.size() == 0)
1285    {
1286       menu.Append(kDeletePresetDummyID, _("Delete Preset"))->Enable(false);
1287    }
1288    else
1289    {
1290       auto sub = std::make_unique<wxMenu>();
1291       for (size_t i = 0, cnt = mUserPresets.size(); i < cnt; i++)
1292       {
1293          sub->Append(kDeletePresetID + i, mUserPresets[i]);
1294       }
1295       menu.Append(0, _("Delete Preset"), sub.release());
1296    }
1297 
1298    menu.AppendSeparator();
1299 
1300    auto factory = mEffect->GetFactoryPresets();
1301 
1302    {
1303       auto sub = std::make_unique<wxMenu>();
1304       sub->Append(kDefaultsID, _("Defaults"));
1305       if (factory.size() > 0)
1306       {
1307          sub->AppendSeparator();
1308          for (size_t i = 0, cnt = factory.size(); i < cnt; i++)
1309          {
1310             auto label = factory[i];
1311             if (label.empty())
1312             {
1313                label = _("None");
1314             }
1315 
1316             sub->Append(kFactoryPresetsID + i, label);
1317          }
1318       }
1319       menu.Append(0, _("Factory Presets"), sub.release());
1320    }
1321 
1322    menu.AppendSeparator();
1323    menu.Append(kImportID, _("Import..."))->Enable(mClient->CanExportPresets());
1324    menu.Append(kExportID, _("Export..."))->Enable(mClient->CanExportPresets());
1325    menu.AppendSeparator();
1326    menu.Append(kOptionsID, _("Options..."))->Enable(mClient->HasOptions());
1327    menu.AppendSeparator();
1328 
1329    {
1330       auto sub = std::make_unique<wxMenu>();
1331 
1332       sub->Append(kDummyID, wxString::Format(_("Type: %s"),
1333                                              ::wxGetTranslation( mEffect->GetFamily().Translation() )));
1334       sub->Append(kDummyID, wxString::Format(_("Name: %s"), mEffect->GetName().Translation()));
1335       sub->Append(kDummyID, wxString::Format(_("Version: %s"), mEffect->GetVersion()));
1336       sub->Append(kDummyID, wxString::Format(_("Vendor: %s"), mEffect->GetVendor().Translation()));
1337       sub->Append(kDummyID, wxString::Format(_("Description: %s"), mEffect->GetDescription().Translation()));
1338       sub->Bind(wxEVT_MENU, [](auto&){}, kDummyID);
1339 
1340       menu.Append(0, _("About"), sub.release());
1341    }
1342 
1343    wxWindow *btn = FindWindow(kMenuID);
1344    wxRect r = btn->GetRect();
1345    BasicMenu::Handle{ &menu }.Popup(
1346       wxWidgetsWindowPlacement{ btn },
1347       { r.GetLeft(), r.GetBottom() }
1348    );
1349 }
1350 
Resume()1351 void EffectUIHost::Resume()
1352 {
1353    if (!mClient->ValidateUI()) {
1354       // If we're previewing we should still be able to stop playback
1355       // so don't disable transport buttons.
1356       //   mEffect->EnableApply(false);   // currently this would also disable transport buttons.
1357       // The preferred behaviour is currently undecided, so for now
1358       // just disallow enabling until settings are valid.
1359       mEnabled = false;
1360       mEnableCb->SetValue(mEnabled);
1361       return;
1362    }
1363    RealtimeEffectManager::Get().RealtimeResumeOne( *mEffect );
1364 }
1365 
OnEnable(wxCommandEvent & WXUNUSED (evt))1366 void EffectUIHost::OnEnable(wxCommandEvent & WXUNUSED(evt))
1367 {
1368    mEnabled = mEnableCb->GetValue();
1369 
1370    if (mEnabled) {
1371       Resume();
1372       mNeedsResume = false;
1373    }
1374    else
1375    {
1376       RealtimeEffectManager::Get().RealtimeSuspendOne( *mEffect );
1377       mNeedsResume = true;
1378    }
1379 
1380    UpdateControls();
1381 }
1382 
OnPlay(wxCommandEvent & WXUNUSED (evt))1383 void EffectUIHost::OnPlay(wxCommandEvent & WXUNUSED(evt))
1384 {
1385    if (!mSupportsRealtime)
1386    {
1387       if (!mClient->ValidateUI() || !mEffect->TransferDataFromWindow())
1388       {
1389          return;
1390       }
1391 
1392       mEffect->Preview(false);
1393 
1394       return;
1395    }
1396 
1397    if (mPlaying)
1398    {
1399       auto gAudioIO = AudioIO::Get();
1400       mPlayPos = gAudioIO->GetStreamTime();
1401       auto &projectAudioManager = ProjectAudioManager::Get( *mProject );
1402       projectAudioManager.Stop();
1403    }
1404    else
1405    {
1406       auto &viewInfo = ViewInfo::Get( *mProject );
1407       const auto &selectedRegion = viewInfo.selectedRegion;
1408       const auto &playRegion = viewInfo.playRegion;
1409       if ( playRegion.Active() )
1410       {
1411          mRegion.setTimes(playRegion.GetStart(), playRegion.GetEnd());
1412          mPlayPos = mRegion.t0();
1413       }
1414       else if (selectedRegion.t0() != mRegion.t0() ||
1415                selectedRegion.t1() != mRegion.t1())
1416       {
1417          mRegion = selectedRegion;
1418          mPlayPos = mRegion.t0();
1419       }
1420 
1421       if (mPlayPos > mRegion.t1())
1422       {
1423          mPlayPos = mRegion.t1();
1424       }
1425 
1426       auto &projectAudioManager = ProjectAudioManager::Get( *mProject );
1427       projectAudioManager.PlayPlayRegion(
1428                                          SelectedRegion(mPlayPos, mRegion.t1()),
1429                                          DefaultPlayOptions( *mProject ),
1430                                          PlayMode::normalPlay );
1431    }
1432 }
1433 
OnRewind(wxCommandEvent & WXUNUSED (evt))1434 void EffectUIHost::OnRewind(wxCommandEvent & WXUNUSED(evt))
1435 {
1436    if (mPlaying)
1437    {
1438       auto gAudioIO = AudioIO::Get();
1439       double seek;
1440       gPrefs->Read(wxT("/AudioIO/SeekShortPeriod"), &seek, 1.0);
1441 
1442       double pos = gAudioIO->GetStreamTime();
1443       if (pos - seek < mRegion.t0())
1444       {
1445          seek = pos - mRegion.t0();
1446       }
1447 
1448       gAudioIO->SeekStream(-seek);
1449    }
1450    else
1451    {
1452       mPlayPos = mRegion.t0();
1453    }
1454 }
1455 
OnFFwd(wxCommandEvent & WXUNUSED (evt))1456 void EffectUIHost::OnFFwd(wxCommandEvent & WXUNUSED(evt))
1457 {
1458    if (mPlaying)
1459    {
1460       double seek;
1461       gPrefs->Read(wxT("/AudioIO/SeekShortPeriod"), &seek, 1.0);
1462 
1463       auto gAudioIO = AudioIO::Get();
1464       double pos = gAudioIO->GetStreamTime();
1465       if (mRegion.t0() < mRegion.t1() && pos + seek > mRegion.t1())
1466       {
1467          seek = mRegion.t1() - pos;
1468       }
1469 
1470       gAudioIO->SeekStream(seek);
1471    }
1472    else
1473    {
1474       // It allows to play past end of selection...probably useless
1475       mPlayPos = mRegion.t1();
1476    }
1477 }
1478 
OnPlayback(wxCommandEvent & evt)1479 void EffectUIHost::OnPlayback(wxCommandEvent & evt)
1480 {
1481    evt.Skip();
1482 
1483    if (evt.GetInt() != 0)
1484    {
1485       if (evt.GetEventObject() != mProject)
1486       {
1487          mDisableTransport = true;
1488       }
1489       else
1490       {
1491          mPlaying = true;
1492       }
1493    }
1494    else
1495    {
1496       mDisableTransport = false;
1497       mPlaying = false;
1498    }
1499 
1500    if (mPlaying)
1501    {
1502       mRegion = ViewInfo::Get( *mProject ).selectedRegion;
1503       mPlayPos = mRegion.t0();
1504    }
1505 
1506    UpdateControls();
1507 }
1508 
OnCapture(wxCommandEvent & evt)1509 void EffectUIHost::OnCapture(wxCommandEvent & evt)
1510 {
1511    evt.Skip();
1512 
1513    if (evt.GetInt() != 0)
1514    {
1515       if (evt.GetEventObject() != mProject)
1516       {
1517          mDisableTransport = true;
1518       }
1519       else
1520       {
1521          mCapturing = true;
1522       }
1523    }
1524    else
1525    {
1526       mDisableTransport = false;
1527       mCapturing = false;
1528    }
1529 
1530    UpdateControls();
1531 }
1532 
OnUserPreset(wxCommandEvent & evt)1533 void EffectUIHost::OnUserPreset(wxCommandEvent & evt)
1534 {
1535    int preset = evt.GetId() - kUserPresetsID;
1536 
1537    mEffect->LoadUserPreset(mEffect->GetUserPresetsGroup(mUserPresets[preset]));
1538 
1539    return;
1540 }
1541 
OnFactoryPreset(wxCommandEvent & evt)1542 void EffectUIHost::OnFactoryPreset(wxCommandEvent & evt)
1543 {
1544    mEffect->LoadFactoryPreset(evt.GetId() - kFactoryPresetsID);
1545 
1546    return;
1547 }
1548 
OnDeletePreset(wxCommandEvent & evt)1549 void EffectUIHost::OnDeletePreset(wxCommandEvent & evt)
1550 {
1551    auto preset = mUserPresets[evt.GetId() - kDeletePresetID];
1552 
1553    int res = AudacityMessageBox(
1554                                 XO("Are you sure you want to delete \"%s\"?").Format( preset ),
1555                                 XO("Delete Preset"),
1556                                 wxICON_QUESTION | wxYES_NO);
1557    if (res == wxYES)
1558    {
1559       mEffect->RemovePrivateConfigSubgroup(mEffect->GetUserPresetsGroup(preset));
1560    }
1561 
1562    LoadUserPresets();
1563 
1564    return;
1565 }
1566 
OnSaveAs(wxCommandEvent & WXUNUSED (evt))1567 void EffectUIHost::OnSaveAs(wxCommandEvent & WXUNUSED(evt))
1568 {
1569    wxTextCtrl *text;
1570    wxString name;
1571    wxDialogWrapper dlg(this, wxID_ANY, XO("Save Preset"));
1572 
1573    ShuttleGui S(&dlg, eIsCreating);
1574 
1575    S.StartPanel();
1576    {
1577       S.StartVerticalLay(1);
1578       {
1579          S.StartHorizontalLay(wxALIGN_LEFT, 0);
1580          {
1581             text = S.AddTextBox(XXO("Preset name:"), name, 30);
1582          }
1583          S.EndHorizontalLay();
1584          S.SetBorder(10);
1585          S.AddStandardButtons();
1586       }
1587       S.EndVerticalLay();
1588    }
1589    S.EndPanel();
1590 
1591    dlg.SetSize(dlg.GetSizer()->GetMinSize());
1592    dlg.Center();
1593    dlg.Fit();
1594 
1595    while (true)
1596    {
1597       int rc = dlg.ShowModal();
1598 
1599       if (rc != wxID_OK)
1600       {
1601          break;
1602       }
1603 
1604       name = text->GetValue();
1605       if (name.empty())
1606       {
1607          AudacityMessageDialog md(
1608                                   this,
1609                                   XO("You must specify a name"),
1610                                   XO("Save Preset") );
1611          md.Center();
1612          md.ShowModal();
1613          continue;
1614       }
1615 
1616       if ( make_iterator_range( mUserPresets ).contains( name ) )
1617       {
1618          AudacityMessageDialog md(
1619                                   this,
1620                                   XO("Preset already exists.\n\nReplace?"),
1621                                   XO("Save Preset"),
1622                                   wxYES_NO | wxCANCEL | wxICON_EXCLAMATION );
1623          md.Center();
1624          int choice = md.ShowModal();
1625          if (choice == wxID_CANCEL)
1626          {
1627             break;
1628          }
1629 
1630          if (choice == wxID_NO)
1631          {
1632             continue;
1633          }
1634       }
1635 
1636       mEffect->SaveUserPreset(mEffect->GetUserPresetsGroup(name));
1637       LoadUserPresets();
1638 
1639       break;
1640    }
1641 
1642    return;
1643 }
1644 
OnImport(wxCommandEvent & WXUNUSED (evt))1645 void EffectUIHost::OnImport(wxCommandEvent & WXUNUSED(evt))
1646 {
1647    mClient->ImportPresets();
1648 
1649    LoadUserPresets();
1650 
1651    return;
1652 }
1653 
OnExport(wxCommandEvent & WXUNUSED (evt))1654 void EffectUIHost::OnExport(wxCommandEvent & WXUNUSED(evt))
1655 {
1656    // may throw
1657    // exceptions are handled in AudacityApp::OnExceptionInMainLoop
1658    mClient->ExportPresets();
1659 
1660    return;
1661 }
1662 
OnOptions(wxCommandEvent & WXUNUSED (evt))1663 void EffectUIHost::OnOptions(wxCommandEvent & WXUNUSED(evt))
1664 {
1665    mClient->ShowOptions();
1666 
1667    return;
1668 }
1669 
OnDefaults(wxCommandEvent & WXUNUSED (evt))1670 void EffectUIHost::OnDefaults(wxCommandEvent & WXUNUSED(evt))
1671 {
1672    mEffect->LoadFactoryDefaults();
1673 
1674    return;
1675 }
1676 
CreateBitmap(const char * const xpm[],bool up,bool pusher)1677 wxBitmap EffectUIHost::CreateBitmap(const char * const xpm[], bool up, bool pusher)
1678 {
1679    wxMemoryDC dc;
1680    wxBitmap pic(xpm);
1681 
1682    wxBitmap mod(pic.GetWidth() + 6, pic.GetHeight() + 6, 24);
1683    dc.SelectObject(mod);
1684 
1685 #if defined(__WXGTK__)
1686    wxColour newColour = wxSystemSettings::GetColour(wxSYS_COLOUR_BACKGROUND);
1687 #else
1688    wxColour newColour = wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE);
1689 #endif
1690 
1691    dc.SetBackground(wxBrush(newColour));
1692    dc.Clear();
1693 
1694    int offset = 3;
1695    if (pusher)
1696    {
1697       if (!up)
1698       {
1699          offset += 1;
1700       }
1701    }
1702 
1703    dc.DrawBitmap(pic, offset, offset, true);
1704 
1705    dc.SelectObject(wxNullBitmap);
1706 
1707    return mod;
1708 }
1709 
UpdateControls()1710 void EffectUIHost::UpdateControls()
1711 {
1712    if (mIsBatch)
1713    {
1714       return;
1715    }
1716 
1717    if (mCapturing || mDisableTransport)
1718    {
1719       // Don't allow focus to get trapped
1720       wxWindow *focus = FindFocus();
1721       if (focus == mRewindBtn || focus == mFFwdBtn || focus == mPlayBtn || focus == mEnableCb)
1722       {
1723          mCloseBtn->SetFocus();
1724       }
1725    }
1726 
1727    mApplyBtn->Enable(!mCapturing);
1728    if (mEffect && (mEffect->GetType() != EffectTypeAnalyze) && (mEffect->GetType() != EffectTypeTool) )
1729    {
1730       (!mIsGUI ? mPlayToggleBtn : mPlayBtn)->Enable(!(mCapturing || mDisableTransport));
1731    }
1732 
1733    if (mSupportsRealtime)
1734    {
1735       mRewindBtn->Enable(!(mCapturing || mDisableTransport));
1736       mFFwdBtn->Enable(!(mCapturing || mDisableTransport));
1737       mEnableCb->Enable(!(mCapturing || mDisableTransport));
1738 
1739       wxBitmapButton *bb;
1740 
1741       if (mPlaying)
1742       {
1743          if (!mIsGUI)
1744          {
1745             /* i18n-hint: The access key "&P" should be the same in
1746              "Stop &Playback" and "Start &Playback" */
1747             mPlayToggleBtn->SetLabel(_("Stop &Playback"));
1748             mPlayToggleBtn->Refresh();
1749          }
1750          else
1751          {
1752             bb = (wxBitmapButton *) mPlayBtn;
1753             bb->SetBitmapLabel(mStopBM);
1754             bb->SetBitmapDisabled(mStopDisabledBM);
1755             bb->SetToolTip(_("Stop"));
1756 #if defined(__WXMAC__)
1757             bb->SetName(_("Stop &Playback"));
1758 #else
1759             bb->SetLabel(_("Stop &Playback"));
1760 #endif
1761          }
1762       }
1763       else
1764       {
1765          if (!mIsGUI)
1766          {
1767             /* i18n-hint: The access key "&P" should be the same in
1768              "Stop &Playback" and "Start &Playback" */
1769             mPlayToggleBtn->SetLabel(_("Start &Playback"));
1770             mPlayToggleBtn->Refresh();
1771          }
1772          else
1773          {
1774             bb = (wxBitmapButton *) mPlayBtn;
1775             bb->SetBitmapLabel(mPlayBM);
1776             bb->SetBitmapDisabled(mPlayDisabledBM);
1777             bb->SetToolTip(_("Play"));
1778 #if defined(__WXMAC__)
1779             bb->SetName(_("Start &Playback"));
1780 #else
1781             bb->SetLabel(_("Start &Playback"));
1782 #endif
1783          }
1784       }
1785    }
1786 }
1787 
LoadUserPresets()1788 void EffectUIHost::LoadUserPresets()
1789 {
1790    mUserPresets.clear();
1791 
1792    if( mEffect )
1793       mEffect->GetPrivateConfigSubgroups(mEffect->GetUserPresetsGroup(wxEmptyString), mUserPresets);
1794 
1795    std::sort( mUserPresets.begin(), mUserPresets.end() );
1796 
1797    return;
1798 }
1799 
InitializeRealtime()1800 void EffectUIHost::InitializeRealtime()
1801 {
1802    if (mSupportsRealtime && !mInitialized)
1803    {
1804       RealtimeEffectManager::Get().RealtimeAddEffect(mEffect);
1805 
1806       wxTheApp->Bind(EVT_AUDIOIO_PLAYBACK,
1807                      &EffectUIHost::OnPlayback,
1808                      this);
1809 
1810       wxTheApp->Bind(EVT_AUDIOIO_CAPTURE,
1811                      &EffectUIHost::OnCapture,
1812                      this);
1813 
1814       mInitialized = true;
1815    }
1816 }
1817 
CleanupRealtime()1818 void EffectUIHost::CleanupRealtime()
1819 {
1820    if (mSupportsRealtime && mInitialized)
1821    {
1822       RealtimeEffectManager::Get().RealtimeRemoveEffect(mEffect);
1823 
1824       mInitialized = false;
1825    }
1826 }
1827 
DialogFactory(wxWindow & parent,EffectHostInterface * pHost,EffectUIClientInterface * client)1828 wxDialog *EffectUI::DialogFactory( wxWindow &parent, EffectHostInterface *pHost,
1829    EffectUIClientInterface *client)
1830 {
1831    auto pEffect = dynamic_cast< Effect* >( pHost );
1832    if ( ! pEffect )
1833       return nullptr;
1834 
1835    // Make sure there is an associated project, whose lifetime will
1836    // govern the lifetime of the dialog, even when the dialog is
1837    // non-modal, as for realtime effects
1838    auto project = FindProjectFromWindow(&parent);
1839    if ( !project )
1840       return nullptr;
1841 
1842    Destroy_ptr<EffectUIHost> dlg{
1843       safenew EffectUIHost{ &parent, *project, pEffect, client} };
1844 
1845    if (dlg->Initialize())
1846    {
1847       // release() is safe because parent will own it
1848       return dlg.release();
1849    }
1850 
1851    return nullptr;
1852 };
1853 
1854 #include "../PluginManager.h"
1855 #include "ProjectRate.h"
1856 #include "../ProjectWindow.h"
1857 #include "../SelectUtilities.h"
1858 #include "../TrackPanel.h"
1859 #include "../WaveTrack.h"
1860 #include "../commands/CommandManager.h"
1861 
1862 /// DoEffect() takes a PluginID and executes the associated effect.
1863 ///
1864 /// At the moment flags are used only to indicate whether to prompt for
1865 //  parameters, whether to save the state to history and whether to allow
1866 /// 'Repeat Last Effect'.
1867 
DoEffect(const PluginID & ID,const CommandContext & context,unsigned flags)1868 /* static */ bool EffectUI::DoEffect(
1869    const PluginID & ID, const CommandContext &context, unsigned flags )
1870 {
1871    AudacityProject &project = context.project;
1872    auto &tracks = TrackList::Get( project );
1873    auto &trackPanel = TrackPanel::Get( project );
1874    auto &trackFactory = WaveTrackFactory::Get( project );
1875    auto rate = ProjectRate::Get(project).GetRate();
1876    auto &selectedRegion = ViewInfo::Get( project ).selectedRegion;
1877    auto &commandManager = CommandManager::Get( project );
1878    auto &window = ProjectWindow::Get( project );
1879 
1880    const PluginDescriptor *plug = PluginManager::Get().GetPlugin(ID);
1881    if (!plug)
1882       return false;
1883 
1884    EffectType type = plug->GetEffectType();
1885 
1886    // Make sure there's no activity since the effect is about to be applied
1887    // to the project's tracks.  Mainly for Apply during RTP, but also used
1888    // for batch commands
1889    if (flags & EffectManager::kConfigured)
1890    {
1891       ProjectAudioManager::Get( project ).Stop();
1892       //Don't Select All if repeating Generator Effect
1893       if (!(flags & EffectManager::kConfigured)) {
1894          SelectUtilities::SelectAllIfNone(project);
1895       }
1896    }
1897 
1898    auto nTracksOriginally = tracks.size();
1899    wxWindow *focus = wxWindow::FindFocus();
1900    wxWindow *parent = nullptr;
1901    if (focus != nullptr) {
1902       parent = focus->GetParent();
1903    }
1904 
1905    bool success = false;
1906    auto cleanup = finally( [&] {
1907 
1908       if (!success) {
1909          // For now, we're limiting realtime preview to a single effect, so
1910          // make sure the menus reflect that fact that one may have just been
1911          // opened.
1912          MenuManager::Get(project).UpdateMenus( false );
1913       }
1914 
1915    } );
1916 
1917    int count = 0;
1918    bool clean = true;
1919    for (auto t : tracks.Selected< const WaveTrack >()) {
1920       if (t->GetEndTime() != 0.0)
1921          clean = false;
1922       count++;
1923    }
1924 
1925    EffectManager & em = EffectManager::Get();
1926 
1927    em.SetSkipStateFlag( false );
1928    if (auto effect = em.GetEffect(ID)) {
1929 #if defined(EXPERIMENTAL_EFFECTS_RACK)
1930       if (effect->SupportsRealtime())
1931       {
1932          EffectRack::Get( context.project ).Add(effect);
1933       }
1934 #endif
1935       effect->SetUIFlags(flags);
1936       success = effect->DoEffect(
1937          rate,
1938          &tracks,
1939          &trackFactory,
1940          selectedRegion,
1941          &window,
1942          (flags & EffectManager::kConfigured) == 0
1943             ? DialogFactory
1944             : nullptr
1945       );
1946    }
1947    else
1948       success = false;
1949 
1950    if (!success)
1951       return false;
1952 
1953    if (em.GetSkipStateFlag())
1954       flags = flags | EffectManager::kSkipState;
1955 
1956    if (!(flags & EffectManager::kSkipState))
1957    {
1958       auto shortDesc = em.GetCommandName(ID);
1959       auto longDesc = em.GetCommandDescription(ID);
1960       ProjectHistory::Get( project ).PushState(longDesc, shortDesc);
1961    }
1962 
1963    if (!(flags & EffectManager::kDontRepeatLast))
1964    {
1965       // Remember a successful generator, effect, analyzer, or tool Process
1966          auto shortDesc = em.GetCommandName(ID);
1967          /* i18n-hint: %s will be the name of the effect which will be
1968           * repeated if this menu item is chosen */
1969          auto lastEffectDesc = XO("Repeat %s").Format(shortDesc);
1970          auto& menuManager = MenuManager::Get(project);
1971          switch ( type ) {
1972          case EffectTypeGenerate:
1973             commandManager.Modify(wxT("RepeatLastGenerator"), lastEffectDesc);
1974             menuManager.mLastGenerator = ID;
1975             menuManager.mRepeatGeneratorFlags = EffectManager::kConfigured;
1976             break;
1977          case EffectTypeProcess:
1978             commandManager.Modify(wxT("RepeatLastEffect"), lastEffectDesc);
1979             menuManager.mLastEffect = ID;
1980             menuManager.mRepeatEffectFlags = EffectManager::kConfigured;
1981             break;
1982          case EffectTypeAnalyze:
1983             commandManager.Modify(wxT("RepeatLastAnalyzer"), lastEffectDesc);
1984             menuManager.mLastAnalyzer = ID;
1985             menuManager.mLastAnalyzerRegistration = MenuCreator::repeattypeplugin;
1986             menuManager.mRepeatAnalyzerFlags = EffectManager::kConfigured;
1987             break;
1988          case EffectTypeTool:
1989             commandManager.Modify(wxT("RepeatLastTool"), lastEffectDesc);
1990             menuManager.mLastTool = ID;
1991             menuManager.mLastToolRegistration = MenuCreator::repeattypeplugin;
1992             menuManager.mRepeatToolFlags = EffectManager::kConfigured;
1993             if (shortDesc == NYQUIST_PROMPT_NAME) {
1994                menuManager.mRepeatToolFlags = EffectManager::kRepeatNyquistPrompt;  //Nyquist Prompt is not configured
1995             }
1996             break;
1997       }
1998    }
1999 
2000    //STM:
2001    //The following automatically re-zooms after sound was generated.
2002    // IMO, it was disorienting, removing to try out without re-fitting
2003    //mchinen:12/14/08 reapplying for generate effects
2004    if (type == EffectTypeGenerate)
2005    {
2006       if (count == 0 || (clean && selectedRegion.t0() == 0.0))
2007          window.DoZoomFit();
2008          //  trackPanel->Refresh(false);
2009    }
2010 
2011    // PRL:  RedrawProject explicitly because sometimes history push is skipped
2012    window.RedrawProject();
2013 
2014    if (focus != nullptr && focus->GetParent()==parent) {
2015       focus->SetFocus();
2016    }
2017 
2018    // A fix for Bug 63
2019    // New tracks added?  Scroll them into view so that user sees them.
2020    // Don't care what track type.  An analyser might just have added a
2021    // Label track and we want to see it.
2022    if( tracks.size() > nTracksOriginally ){
2023       // 0.0 is min scroll position, 1.0 is max scroll position.
2024       trackPanel.VerticalScroll( 1.0 );
2025    }
2026    else {
2027       auto pTrack = *tracks.Selected().begin();
2028       if (!pTrack)
2029          pTrack = *tracks.Any().begin();
2030       if (pTrack) {
2031          TrackFocus::Get(project).Set(pTrack);
2032          pTrack->EnsureVisible();
2033       }
2034    }
2035 
2036    return true;
2037 }
2038 
2039 ///////////////////////////////////////////////////////////////////////////////
BEGIN_EVENT_TABLE(EffectDialog,wxDialogWrapper)2040 BEGIN_EVENT_TABLE(EffectDialog, wxDialogWrapper)
2041    EVT_BUTTON(wxID_OK, EffectDialog::OnOk)
2042 END_EVENT_TABLE()
2043 
2044 EffectDialog::EffectDialog(wxWindow * parent,
2045                            const TranslatableString & title,
2046                            int type,
2047                            int flags,
2048                            int additionalButtons)
2049 : wxDialogWrapper(parent, wxID_ANY, title, wxDefaultPosition, wxDefaultSize, flags)
2050 {
2051    mType = type;
2052    mAdditionalButtons = additionalButtons;
2053 }
2054 
Init()2055 void EffectDialog::Init()
2056 {
2057    long buttons = eOkButton;
2058    if ((mType != EffectTypeAnalyze) && (mType != EffectTypeTool))
2059    {
2060       buttons |= eCancelButton;
2061       if (mType == EffectTypeProcess)
2062       {
2063          buttons |= ePreviewButton;
2064       }
2065    }
2066 
2067    ShuttleGui S(this, eIsCreating);
2068 
2069    S.SetBorder(5);
2070    S.StartVerticalLay(true);
2071    {
2072       PopulateOrExchange(S);
2073       S.AddStandardButtons(buttons|mAdditionalButtons);
2074    }
2075    S.EndVerticalLay();
2076 
2077    Layout();
2078    Fit();
2079    SetMinSize(GetSize());
2080    Center();
2081 }
2082 
2083 /// This is a virtual function which will be overridden to
2084 /// provide the actual parameters that we want for each
2085 /// kind of dialog.
PopulateOrExchange(ShuttleGui & WXUNUSED (S))2086 void EffectDialog::PopulateOrExchange(ShuttleGui & WXUNUSED(S))
2087 {
2088    return;
2089 }
2090 
TransferDataToWindow()2091 bool EffectDialog::TransferDataToWindow()
2092 {
2093    ShuttleGui S(this, eIsSettingToDialog);
2094    PopulateOrExchange(S);
2095 
2096    return true;
2097 }
2098 
TransferDataFromWindow()2099 bool EffectDialog::TransferDataFromWindow()
2100 {
2101    ShuttleGui S(this, eIsGettingFromDialog);
2102    PopulateOrExchange(S);
2103 
2104    return true;
2105 }
2106 
Validate()2107 bool EffectDialog::Validate()
2108 {
2109    return true;
2110 }
2111 
OnPreview(wxCommandEvent & WXUNUSED (evt))2112 void EffectDialog::OnPreview(wxCommandEvent & WXUNUSED(evt))
2113 {
2114    return;
2115 }
2116 
OnOk(wxCommandEvent & WXUNUSED (evt))2117 void EffectDialog::OnOk(wxCommandEvent & WXUNUSED(evt))
2118 {
2119    // On wxGTK (wx2.8.12), the default action is still executed even if
2120    // the button is disabled.  This appears to affect all wxDialogs, not
2121    // just our Effects dialogs.  So, this is a only temporary workaround
2122    // for legacy effects that disable the OK button.  Hopefully this has
2123    // been corrected in wx3.
2124    if (FindWindow(wxID_OK)->IsEnabled() && Validate() && TransferDataFromWindow())
2125    {
2126       EndModal(true);
2127    }
2128 
2129    return;
2130 }
2131