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