Audacity 3.2.0
RealtimeEffectPanel.cpp
Go to the documentation of this file.
1/*!********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 @file RealtimeEffectPanel.cpp
6
7 @author Vitaly Sverchinsky
8
9**********************************************************************/
10
11#include "RealtimeEffectPanel.h"
12
13#include <wx/app.h>
14#include <wx/sizer.h>
15#include <wx/splitter.h>
16#include <wx/statbmp.h>
17#include <wx/stattext.h>
18#include <wx/menu.h>
19#include <wx/wupdlock.h>
20#include <wx/hyperlink.h>
21
22#include <wx/dcbuffer.h>
23
24#include "HelpSystem.h"
25#include "Theme.h"
26#include "AllThemeResources.h"
27#include "AudioIO.h"
28#include "BasicUI.h"
29#include "Observer.h"
30#include "PluginManager.h"
31#include "Project.h"
32#include "ProjectHistory.h"
33#include "ProjectWindow.h"
34#include "ProjectWindows.h"
35#include "TrackFocus.h"
36#include "AColor.h"
37#include "WaveTrack.h"
38#include "effects/EffectUI.h"
40#include "RealtimeEffectList.h"
41#include "RealtimeEffectState.h"
43#include "UndoManager.h"
44#include "PendingTracks.h"
45#include "Prefs.h"
46#include "BasicUI.h"
48#include "ListNavigationPanel.h"
49#include "MovableControl.h"
50#include "menus/MenuHelper.h"
51#include "prefs/EffectsPrefs.h"
52
53#if wxUSE_ACCESSIBILITY
54#include "WindowAccessible.h"
55#endif
56
57namespace
58{
59 using namespace MenuRegistry;
60 class RealtimeEffectsMenuVisitor final : public Visitor<Traits> {
61 wxMenu& mMenu;
62 wxMenu* mMenuPtr { nullptr };
63 int mMenuItemIdCounter { wxID_HIGHEST };
64 std::vector<Identifier> mIndexedPluginList;
65 int mMenuLevelCounter { 0 };
66 public:
67 RealtimeEffectsMenuVisitor(wxMenu& menu) : Visitor<Traits>{ std::tuple{
68 [this](const MenuRegistry::MenuItem &menuItem, const auto&) {
69 //Don't create a group item for root
70 if (mMenuLevelCounter != 0)
71 {
72 auto submenu = std::make_unique<wxMenu>();
73 mMenuPtr->AppendSubMenu(submenu.get(), menuItem.GetTitle().Translation());
74 mMenuPtr = submenu.release();
75 }
76 ++mMenuLevelCounter;
77 },
78
79 [this](const MenuRegistry::CommandItem &commandItem, const auto&) {
80 mMenuPtr->Append(mMenuItemIdCounter, commandItem.label_in.Translation());
81 mIndexedPluginList.push_back(commandItem.name);
82 ++mMenuItemIdCounter;
83 },
84
85 [this](const MenuRegistry::MenuItem &, const auto&) {
86 --mMenuLevelCounter;
87 if (mMenuLevelCounter != 0)
88 {
89 assert(mMenuPtr->GetParent() != nullptr);
90 mMenuPtr = mMenuPtr->GetParent();
91 }
92 }},
93
94 [this]() {
95 mMenuPtr->AppendSeparator();
96 }}
97 , mMenu(menu), mMenuPtr(&mMenu)
98 {}
99
100 Identifier GetPluginID(int menuIndex) const
101 {
102 assert(menuIndex >= wxID_HIGHEST && menuIndex < (wxID_HIGHEST + mIndexedPluginList.size()));
103 return mIndexedPluginList[menuIndex - wxID_HIGHEST];
104 }
105 };
106
108 : public PrefsListener
109 {
110 std::shared_ptr<MenuRegistry::MenuItem> mCachedMenu;
112 public:
113
114 static std::optional<wxString> PickEffect(AudacityProject& project, wxWindow* parent, const wxString& selectedEffectID)
115 {
116 wxMenu menu;
117 if(!selectedEffectID.empty())
118 {
119 //no need to handle language change since menu creates its own event loop
120 menu.Append(wxID_REMOVE, _("No Effect"));
121 menu.AppendSeparator();
122 }
123
124 RealtimeEffectsMenuVisitor visitor { menu };
125
126 Get().Populate(project, visitor);
127
128 int commandId = wxID_NONE;
129
130#if defined(__WXMSW__) || defined(__WXMAC__)
131 menu.AppendSeparator();
132 menu.Append(wxID_MORE, _("Get more effects..."));
133#endif
134 menu.Bind(wxEVT_MENU, [&](wxCommandEvent evt) { commandId = evt.GetId(); });
135
136 if(parent->PopupMenu(&menu, parent->GetClientRect().GetLeftBottom()) && commandId != wxID_NONE)
137 {
138 if(commandId == wxID_REMOVE)
139 return wxString {};
140 if(commandId == wxID_MORE)
141 OpenInDefaultBrowser("https://www.musehub.com");
142 else
143 return visitor.GetPluginID(commandId).GET();
144 }
145
146 return {};
147 }
148
149 private:
150
152 {
153 static EffectsMenuHelper helper;
154 return helper;
155 }
156
158 {
159 auto cachedMenuItem = GetMenuItem();
160 if(!cachedMenuItem)
161 return;
162
163 VisitWithFunctions(visitor, cachedMenuItem.get(), {}, project);
164 }
165
167 {
168 mPluginsChangedSubscription = PluginManager::Get().Subscribe(
170 {
171 mCachedMenu.reset();
172 }
173 );
174 }
175
176 std::shared_ptr<MenuItem> GetMenuItem()
177 {
178 if(!mCachedMenu)
179 UpdateEffectMenuItems();
180
181 assert(mCachedMenu);
182 if(mCachedMenu)
183 return mCachedMenu;
184 return {};
185 }
186
187 void UpdatePrefs() override
188 {
189 mCachedMenu.reset();
190 }
191
193 {
194 using namespace MenuRegistry;
195 auto root = std::shared_ptr{ Menu("", TranslatableString{}) };
196
197 static auto realtimeEffectPredicate = [](const PluginDescriptor& desc)
198 {
199 return desc.IsEffectRealtime();
200 };
201
202 const auto groupby = RealtimeEffectsGroupBy.Read();
203
204 auto analyzeSection = Section("", Menu("", XO("Analyze")));
205 auto submenu =
206 static_cast<MenuItem*>(analyzeSection->begin()->get());
208 *submenu,
210 {}, groupby, nullptr,
211 realtimeEffectPredicate
212 );
213
214 if(!submenu->empty())
215 {
216 root->push_back(move(analyzeSection));
217 }
218
220 *root,
222 {}, groupby, nullptr,
223 realtimeEffectPredicate
224 );
225
226 mCachedMenu.swap(root);
227 }
228 };
229
231 auto desc = PluginManager::Get().GetPlugin(ID);
232 return desc;
233 }
234
236 {
237 const auto &ID = state.GetID();
238 const auto desc = GetPlugin(ID);
239 return desc
240 ? desc->GetSymbol().Msgid()
241 : XO("%s (missing)")
242 .Format(PluginManager::GetEffectNameFromID(ID).GET());
243 }
244
245 template <typename Visitor>
247 {
248 effects.Visit(
249 [visitor](auto& effectState, bool)
250 {
251 auto& ui = RealtimeEffectStateUI::Get(effectState);
252 visitor(ui);
253 });
254 }
255
256 template <typename Visitor>
257 void VisitRealtimeEffectStateUIs(const Track& track, Visitor&& visitor)
258 {
259 VisitRealtimeEffectStateUIs(RealtimeEffectList::Get(track), std::forward<Visitor>(visitor));
260 }
261
263 {
264 const auto& name = track.GetName();
266 RealtimeEffectList::Get(track), [&](auto& ui) { ui.SetTargetName(name); });
267 }
268
270 {
271 const auto& name = project.GetProjectName();
273 RealtimeEffectList::Get(project), [&](auto& ui) { ui.SetTargetName(name); }
274 );
275 }
276
278 {
281 [&](auto& ui)
282 {
283 if (ui.IsShown())
284 {
285 ui.Hide(&project);
286 ui.Show(project);
287 }
288 });
289 }
290 //fwd
291 class RealtimeEffectControl;
292
293 class DropHintLine : public wxWindow
294 {
295 public:
296 DropHintLine(wxWindow *parent,
297 wxWindowID id,
298 const wxPoint& pos = wxDefaultPosition,
299 const wxSize& size = wxDefaultSize)
300 : wxWindow(parent, id, pos, size, wxNO_BORDER, wxEmptyString)
301 {
302 wxWindow::SetBackgroundStyle(wxBG_STYLE_PAINT);
303 Bind(wxEVT_PAINT, &DropHintLine::OnPaint, this);
304 }
305
306 bool AcceptsFocus() const override { return false; }
307
308 private:
309 void OnPaint(wxPaintEvent&)
310 {
311 wxBufferedPaintDC dc(this);
312 const auto rect = wxRect(GetSize());
313
314 dc.SetPen(*wxTRANSPARENT_PEN);
315 dc.SetBrush(GetBackgroundColour());
316 dc.DrawRectangle(rect);
317 }
318 };
319
320 class HyperLinkCtrlWrapper : public ListNavigationEnabled<wxHyperlinkCtrl>
321 {
322 public:
323 HyperLinkCtrlWrapper(wxWindow *parent,
324 wxWindowID id,
325 const wxString& label,
326 const wxString& url,
327 const wxPoint& pos = wxDefaultPosition,
328 const wxSize& size = wxDefaultSize,
329 long style = wxHL_DEFAULT_STYLE,
330 const wxString& name = wxHyperlinkCtrlNameStr)
331 {
332 Create(parent, id, label, url, pos, size, style, name);
333 }
334
335 void Create(wxWindow *parent,
336 wxWindowID id,
337 const wxString& label,
338 const wxString& url,
339 const wxPoint& pos = wxDefaultPosition,
340 const wxSize& size = wxDefaultSize,
341 long style = wxHL_DEFAULT_STYLE,
342 const wxString& name = wxHyperlinkCtrlNameStr)
343 {
345 Bind(wxEVT_PAINT, &HyperLinkCtrlWrapper::OnPaint, this);
346 }
347
348 void OnPaint(wxPaintEvent& evt)
349 {
350 wxPaintDC dc(this);
351 dc.SetFont(GetFont());
352 dc.SetTextForeground(GetForegroundColour());
353 dc.SetTextBackground(GetBackgroundColour());
354
355 auto labelRect = GetLabelRect();
356
357 dc.DrawText(GetLabel(), labelRect.GetTopLeft());
358 if (HasFocus())
359 AColor::DrawFocus(dc, labelRect);
360 }
361 };
362
363#if wxUSE_ACCESSIBILITY
364 class RealtimeEffectControlAx : public wxAccessible
365 {
366 public:
367 RealtimeEffectControlAx(wxWindow* win = nullptr) : wxAccessible(win) { }
368
369 wxAccStatus GetName(int childId, wxString* name) override
370 {
371 if(childId != wxACC_SELF)
372 return wxACC_NOT_IMPLEMENTED;
373
374 if(auto movable = wxDynamicCast(GetWindow(), MovableControl))
375 //i18n-hint: argument - position of the effect in the effect stack
376 *name = wxString::Format(_("Effect %d"), movable->FindIndexInParent() + 1);
377 return wxACC_OK;
378 }
379
380 wxAccStatus GetChildCount(int* childCount) override
381 {
382 const auto window = GetWindow();
383 *childCount = window->GetChildren().size();
384 return wxACC_OK;
385 }
386
387 wxAccStatus GetChild(int childId, wxAccessible** child) override
388 {
389 if(childId == wxACC_SELF)
390 *child = this;
391 else
392 {
393 const auto window = GetWindow();
394 const auto& children = window->GetChildren();
395 const auto childIndex = childId - 1;
396 if(childIndex < children.size())
397 *child = children[childIndex]->GetAccessible();
398 else
399 *child = nullptr;
400 }
401 return wxACC_OK;
402 }
403
404 wxAccStatus GetRole(int childId, wxAccRole* role) override
405 {
406 if(childId != wxACC_SELF)
407 return wxACC_NOT_IMPLEMENTED;
408
409 *role = wxROLE_SYSTEM_PANE;
410 return wxACC_OK;
411 }
412
413 wxAccStatus GetState(int childId, long* state) override
414 {
415 if(childId != wxACC_SELF)
416 return wxACC_NOT_IMPLEMENTED;
417
418 const auto window = GetWindow();
419 if(!window->IsEnabled())
420 *state = wxACC_STATE_SYSTEM_UNAVAILABLE;
421 else
422 {
423 *state = wxACC_STATE_SYSTEM_FOCUSABLE;
424 if(window->HasFocus())
425 *state |= wxACC_STATE_SYSTEM_FOCUSED;
426 }
427 return wxACC_OK;
428 }
429 };
430#endif
431
433 {
434 public:
435 virtual ~EffectListUIDelegate() = default;
436
438 virtual wxString GetSourceName() = 0;
440 };
441
442 //UI control that represents individual effect from the effect list
443 class RealtimeEffectControl : public ListNavigationEnabled<MovableControl>
444 {
445 wxWeakRef<AudacityProject> mProject;
446 std::shared_ptr<EffectListUIDelegate> mDelegate;
447 //std::shared_ptr<SampleTrack> mTrack;
448 std::shared_ptr<RealtimeEffectState> mEffectState;
449 std::shared_ptr<EffectSettingsAccess> mSettingsAccess;
450
451 ThemedAButtonWrapper<AButton>* mChangeButton{nullptr};
452 AButton* mEnableButton{nullptr};
454
456
457 public:
459
460 RealtimeEffectControl(wxWindow* parent,
461 wxWindowID winid,
462 const wxPoint& pos = wxDefaultPosition,
463 const wxSize& size = wxDefaultSize)
464 {
465 Create(parent, winid, pos, size);
466 }
467
468 void Create(wxWindow* parent,
469 wxWindowID winid,
470 const wxPoint& pos = wxDefaultPosition,
471 const wxSize& size = wxDefaultSize)
472 {
473
474 //Prevents flickering and paint order issues
475 MovableControl::SetBackgroundStyle(wxBG_STYLE_PAINT);
476 MovableControl::Create(parent, winid, pos, size, wxNO_BORDER | wxWANTS_CHARS);
477
478 Bind(wxEVT_PAINT, &RealtimeEffectControl::OnPaint, this);
479 Bind(wxEVT_SET_FOCUS, &RealtimeEffectControl::OnFocusChange, this);
480 Bind(wxEVT_KILL_FOCUS, &RealtimeEffectControl::OnFocusChange, this);
481
482 auto sizer = std::make_unique<wxBoxSizer>(wxHORIZONTAL);
483
484 //On/off button
485 auto enableButton = safenew ThemedAButtonWrapper<AButton>(this);
486 enableButton->SetTranslatableLabel(XO("Power"));
487 enableButton->SetImageIndices(0, bmpEffectOff, bmpEffectOff, bmpEffectOn, bmpEffectOn, bmpEffectOff);
488 enableButton->SetButtonToggles(true);
489 enableButton->SetBackgroundColorIndex(clrEffectListItemBackground);
490 mEnableButton = enableButton;
491
492 enableButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
493 if(mDelegate && mEffectState)
494 {
495 mEffectState->SetActive(mEnableButton->IsDown());
496 if (mProject)
497 {
498 ProjectHistory::Get(*mProject).ModifyState(false);
499 UndoManager::Get(*mProject).MarkUnsaved();
500 }
501 }
502 });
503
504 //Central button with effect name, show settings
505 const auto optionsButton = safenew ThemedAButtonWrapper<AButton>(this, wxID_ANY);
506 optionsButton->SetImageIndices(0,
507 bmpHButtonNormal,
508 bmpHButtonHover,
509 bmpHButtonDown,
510 bmpHButtonHover,
511 bmpHButtonDisabled);
512 optionsButton->SetBackgroundColorIndex(clrEffectListItemBackground);
513 optionsButton->SetForegroundColorIndex(clrTrackPanelText);
514 optionsButton->SetButtonType(AButton::TextButton);
515 optionsButton->Bind(wxEVT_BUTTON, &RealtimeEffectControl::OnOptionsClicked, this);
516
517 //Remove/replace effect
518 auto changeButton = safenew ThemedAButtonWrapper<AButton>(this);
519 changeButton->SetImageIndices(0, bmpMoreNormal, bmpMoreHover, bmpMoreDown, bmpMoreHover, bmpMoreDisabled);
520 changeButton->SetBackgroundColorIndex(clrEffectListItemBackground);
521 changeButton->SetTranslatableLabel(XO("Replace effect"));
522 changeButton->Bind(wxEVT_BUTTON, &RealtimeEffectControl::OnChangeButtonClicked, this);
523
524 auto dragArea = safenew wxStaticBitmap(this, wxID_ANY, theTheme.Bitmap(bmpDragArea));
525 dragArea->Disable();
526 sizer->Add(dragArea, 0, wxLEFT | wxCENTER, 5);
527 sizer->Add(enableButton, 0, wxLEFT | wxCENTER, 5);
528 sizer->Add(optionsButton, 1, wxLEFT | wxCENTER, 5);
529 sizer->Add(changeButton, 0, wxLEFT | wxRIGHT | wxCENTER, 5);
530 mChangeButton = changeButton;
531 mOptionsButton = optionsButton;
532
533 auto vSizer = std::make_unique<wxBoxSizer>(wxVERTICAL);
534 vSizer->Add(sizer.release(), 0, wxUP | wxDOWN | wxEXPAND, 10);
535
536 SetSizer(vSizer.release());
537
538 SetInTabOrder(false);
539
540#if wxUSE_ACCESSIBILITY
541 SetAccessible(safenew RealtimeEffectControlAx(this));
542#endif
543 }
544
546 const std::shared_ptr<EffectListUIDelegate>& delegate,
547 const std::shared_ptr<RealtimeEffectState> &pState)
548 {
549 mProject = &project;
550 mDelegate = delegate;
551 mEffectState = pState;
552
553 mSubscription = mEffectState->Subscribe([this](RealtimeEffectStateChange state) {
555 ? mEnableButton->PushDown()
556 : mEnableButton->PopUp();
557 });
558
560 if (pState) {
561 label = GetEffectName(*mEffectState);
562 mSettingsAccess = pState->GetAccess();
563 }
564 else
565 mSettingsAccess.reset();
566 if (mEnableButton)
567 mSettingsAccess && mSettingsAccess->Get().extra.GetActive()
568 ? mEnableButton->PushDown()
569 : mEnableButton->PopUp();
570 if (mOptionsButton)
571 {
572 mOptionsButton->SetTranslatableLabel(label);
573 mOptionsButton->SetEnabled(pState && GetPlugin(pState->GetID()));
574 }
575 }
576
578 {
579 if(!mDelegate || !mProject || mEffectState == nullptr)
580 return;
581
582 auto& ui = RealtimeEffectStateUI::Get(*mEffectState);
583 // Don't need autosave for the effect that is being removed
584 ui.Hide();
585
586 //After AudioIO::RemoveState call this will be destroyed
587 auto effectName = GetEffectName(*mEffectState);
588
589 AudioIO::Get()->RemoveState(*mProject, mDelegate->GetChannelGroup(), std::move(mEffectState));
595 XO("Removed %s from %s").Format(effectName, mDelegate->GetSourceName()),
598 XO("Remove %s").Format(effectName)
599 );
600 }
601
602 void OnOptionsClicked(wxCommandEvent& event)
603 {
604 if(!mDelegate || !mEffectState)
605 return;//not initialized
606
607 const auto ID = mEffectState->GetID();
608 const auto effectPlugin = EffectManager::Get().GetEffect(ID);
609
610 if(effectPlugin == nullptr)
611 {
613 return;
614 }
615
616 if(!mProject)
617 return;
618 auto& effectStateUI = RealtimeEffectStateUI::Get(*mEffectState);
619
620 effectStateUI.SetTargetName(mDelegate->GetSourceName());
621 effectStateUI.Toggle(*mProject);
622 }
623
624 void OnChangeButtonClicked(wxCommandEvent& event)
625 {
626 if(!mDelegate || mProject == nullptr)
627 return;
628
629 if(mEffectState == nullptr)
630 return;//not initialized
631
632 const auto effectID = EffectsMenuHelper::PickEffect(
633 *mProject,
634 mChangeButton,
635 mEffectState->GetID()
636 );
637 if(!effectID)
638 return;//nothing
639
640 if(effectID->empty())
641 RemoveFromList();
642 else
643 {
644 auto &project = *mProject;
646 auto oIndex = em.FindState(mDelegate->GetChannelGroup(), mEffectState);
647 if (!oIndex)
648 return;
649
650 auto oldName = GetEffectName(*mEffectState);
651
652 if (auto state = AudioIO::Get()
653 ->ReplaceState(project, mDelegate->GetChannelGroup(), *oIndex, *effectID)
654 ){
655 // Message subscription took care of updating the button text
656 // and destroyed `this`!
657 auto effect = state->GetEffect();
658 assert(effect); // postcondition of ReplaceState
660 /*i18n-hint: undo history,
661 first and second parameters - realtime effect names
662 */
663 XO("Replaced %s with %s")
664 .Format(oldName, effect->GetName()),
667 XO("Replace %s").Format(oldName));
668 }
669 }
670 }
671
672 void OnPaint(wxPaintEvent&)
673 {
674 wxBufferedPaintDC dc(this);
675 const auto rect = wxRect(GetSize());
676
677 dc.SetPen(*wxTRANSPARENT_PEN);
678 dc.SetBrush(GetBackgroundColour());
679 dc.DrawRectangle(rect);
680
681 dc.SetPen(theTheme.Colour(clrEffectListItemBorder));
682 dc.SetBrush(theTheme.Colour(clrEffectListItemBorder));
683 dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight());
684
685 if(HasFocus())
686 AColor::DrawFocus(dc, GetClientRect().Deflate(3, 3));
687 }
688
689 void OnFocusChange(wxFocusEvent& evt)
690 {
691 Refresh(false);
692 evt.Skip();
693 }
694 };
695
696 static wxString GetSafeVendor(const PluginDescriptor& descriptor)
697 {
698 if (descriptor.GetVendor().empty())
699 return XO("Unknown").Translation();
700
701 return descriptor.GetVendor();
702 }
703}
704
706 : public EffectListUIDelegate
707{
708 std::shared_ptr<SampleTrack> mTrack;
709public:
710
711 TrackEffectListUIDelegate(std::shared_ptr<SampleTrack> track)
712 : mTrack(std::move(track))
713 {
715 }
716
718 {
720 }
721
722 wxString GetSourceName() override
723 {
724 return mTrack->GetName();
725 }
726
728 {
729 return mTrack.get();
730 }
731};
732
734 : public EffectListUIDelegate
735{
736 std::shared_ptr<RealtimeEffectList> mEffectList;
737public:
738
740 {
741 mEffectList = RealtimeEffectList::Get(project).shared_from_this();
743 }
744
746 {
747 return *mEffectList;
748 }
749
750 wxString GetSourceName() override
751 {
752 //i18n-hint: master channel display name
753 return _("Master");
754 }
756 {
757 return nullptr;
758 }
759};
760
762 : public wxScrolledWindow
763{
764 wxWeakRef<AudacityProject> mProject;
766 wxWindow* mEffectListContainer{nullptr};
767 wxWindow* mFooter{nullptr};
768
769 std::shared_ptr<EffectListUIDelegate> mDelegate;
770
772
773public:
774 RealtimeEffectListWindow(wxWindow *parent,
775 wxWindowID winid = wxID_ANY,
776 const wxPoint& pos = wxDefaultPosition,
777 const wxSize& size = wxDefaultSize,
778 long style = wxScrolledWindowStyle,
779 const wxString& name = wxPanelNameStr)
780 : wxScrolledWindow(parent, winid, pos, size, style, name)
781 {
782#ifdef __WXMSW__
783 //Fixes flickering on redraw
784 wxScrolledWindow::SetDoubleBuffered(true);
785#endif
786 auto rootSizer = std::make_unique<wxBoxSizer>(wxVERTICAL);
787
788 auto effectListContainer = safenew ThemedWindowWrapper<wxPanel>(this, wxID_ANY);
789 effectListContainer->SetBackgroundColorIndex(clrMedium);
790 effectListContainer->SetSizer(safenew wxBoxSizer(wxVERTICAL));
791 effectListContainer->SetDoubleBuffered(true);
792 effectListContainer->Hide();
793 mEffectListContainer = effectListContainer;
794
795 auto addEffect = safenew ThemedAButtonWrapper<AButton>(this, wxID_ANY);
796 addEffect->SetImageIndices(0,
797 bmpHButtonNormal,
798 bmpHButtonHover,
799 bmpHButtonDown,
800 bmpHButtonHover,
801 bmpHButtonDisabled);
802 addEffect->SetTranslatableLabel(XO("Add effect"));
803 addEffect->SetButtonType(AButton::TextButton);
804 addEffect->SetBackgroundColorIndex(clrMedium);
805 addEffect->SetForegroundColorIndex(clrTrackPanelText);
806 addEffect->Bind(wxEVT_BUTTON, &RealtimeEffectListWindow::OnAddEffectClicked, this);
807 mAddEffect = addEffect;
808
809 //indicates the insertion position of the item
810 auto dropHintLine = safenew ThemedWindowWrapper<DropHintLine>(effectListContainer, wxID_ANY);
811 dropHintLine->SetBackgroundColorIndex(clrDropHintHighlight);
812 dropHintLine->Hide();
813
814 rootSizer->Add(mEffectListContainer, 0, wxEXPAND | wxBOTTOM, 10);
815 rootSizer->Add(addEffect, 0, wxLEFT | wxRIGHT | wxBOTTOM | wxEXPAND, 20);
816
817 SetSizer(rootSizer.release());
818 SetMinSize({});
819
820 Bind(EVT_MOVABLE_CONTROL_DRAG_STARTED, [dropHintLine](const MovableControlEvent& event)
821 {
822 if(auto window = dynamic_cast<wxWindow*>(event.GetEventObject()))
823 window->Raise();
824 });
825 Bind(EVT_MOVABLE_CONTROL_DRAG_POSITION, [this, dropHintLine](const MovableControlEvent& event)
826 {
827 constexpr auto DropHintLineHeight { 3 };//px
828
829 auto sizer = mEffectListContainer->GetSizer();
830 assert(sizer != nullptr);
831
832 if(event.GetSourceIndex() == event.GetTargetIndex())
833 {
834 //do not display hint line if position didn't change
835 dropHintLine->Hide();
836 return;
837 }
838
839 if(!dropHintLine->IsShown())
840 {
841 dropHintLine->Show();
842 dropHintLine->Raise();
843 if(auto window = dynamic_cast<wxWindow*>(event.GetEventObject()))
844 window->Raise();
845 }
846
847 auto item = sizer->GetItem(event.GetTargetIndex());
848 dropHintLine->SetSize(item->GetSize().x, DropHintLineHeight);
849
850 if(event.GetTargetIndex() > event.GetSourceIndex())
851 dropHintLine->SetPosition(item->GetRect().GetBottomLeft() - wxPoint(0, DropHintLineHeight));
852 else
853 dropHintLine->SetPosition(item->GetRect().GetTopLeft());
854 });
855
856 Bind(EVT_MOVABLE_CONTROL_DRAG_FINISHED, [this, dropHintLine](const MovableControlEvent& event)
857 {
858 dropHintLine->Hide();
859
860 if(!mDelegate || !mProject)
861 return;
862
863 const auto from = event.GetSourceIndex();
864 const auto to = event.GetTargetIndex();
865 if(from != to)
866 {
867 auto& effectList = mDelegate->GetEffectList();
868 auto effectName =
869 effectList.GetStateAt(from)->GetEffect()->GetName();
870 bool up = (to < from);
871 effectList.MoveEffect(from, to);
873 (up
878 ? XO("Moved %s up in %s")
883 : XO("Moved %s down in %s"))
884 .Format(effectName, mDelegate->GetSourceName()),
885 XO("Change effect order"), UndoPush::CONSOLIDATE);
886 }
887 else
888 {
889 wxWindowUpdateLocker freeze(this);
890 Layout();
891 }
892 });
893
894 SetScrollRate(0, 20);
895#if defined(__WXMSW__) || defined(__WXMAC__)
896#endif
897 }
898
899 void SetFooter(wxWindow* footer)
900 {
901 if(footer == mFooter)
902 return;
903
904 if(mFooter != nullptr)
905 {
906 GetSizer()->Detach(mFooter);
907 mFooter->Destroy();
908 }
909
910 mFooter = footer;
911 GetSizer()->Add(mFooter, 0, wxEXPAND);
912
913 Layout();
914 }
915
917 {
918 auto sizer = mEffectListContainer->GetSizer();
919 const auto insertItem = [this, &msg](){
920 auto& effects = mDelegate->GetEffectList();
921 InsertEffectRow(msg.srcIndex, effects.GetStateAt(msg.srcIndex));
922 if(mFooter != nullptr)
923 mFooter->Hide();
924 };
925 const auto removeItem = [&](){
927 // Don't need to auto-save changed settings of effect that is deleted
928 // Undo history push will do it anyway
929 ui.Hide();
930
931 auto window = sizer->GetItem(msg.srcIndex)->GetWindow();
932 sizer->Remove(msg.srcIndex);
933 wxTheApp->CallAfter([ref = wxWeakRef { window }] {
934 if(ref) ref->Destroy();
935 });
936
937 if(sizer->IsEmpty())
938 {
939 if(mEffectListContainer->IsDescendant(FindFocus()))
940 mAddEffect->SetFocus();
941
942 mEffectListContainer->Hide();
943 if(mFooter != nullptr)
944 mFooter->Show();
945 }
946 };
947
948 wxWindowUpdateLocker freeze(this);
950 {
951 const auto sizer = mEffectListContainer->GetSizer();
952
953 const auto movedItem = sizer->GetItem(msg.srcIndex);
954
955 const auto proportion = movedItem->GetProportion();
956 const auto flag = movedItem->GetFlag();
957 const auto border = movedItem->GetBorder();
958 const auto window = movedItem->GetWindow();
959
960 if(msg.srcIndex < msg.dstIndex)
961 window->MoveAfterInTabOrder(sizer->GetItem(msg.dstIndex)->GetWindow());
962 else
963 window->MoveBeforeInTabOrder(sizer->GetItem(msg.dstIndex)->GetWindow());
964
965 sizer->Remove(msg.srcIndex);
966 sizer->Insert(msg.dstIndex, window, proportion, flag, border);
967 }
969 {
970 insertItem();
971 }
973 {
974 removeItem();
975 }
977 {
978 insertItem();
979 }
981 {
982 removeItem();
983 }
984 SendSizeEventToParent();
985 }
986
988 {
990
991 mProject.Release();
992 mDelegate.reset();
994 }
995
996 void SetDelegate(AudacityProject& project, const std::shared_ptr<EffectListUIDelegate>& delegate)
997 {
999
1000 mProject = &project;
1001 mDelegate = delegate;
1003
1004 if (mDelegate)
1005 {
1006 mEffectListItemMovedSubscription = mDelegate->GetEffectList().Subscribe(
1008 }
1009 }
1010
1011 void EnableEffects(bool enable)
1012 {
1013 if (mDelegate)
1014 mDelegate->GetEffectList().SetActive(enable);
1015 }
1016
1018 {
1019 wxWindowUpdateLocker freeze(this);
1020
1021 //delete items that were added to the sizer
1022 mEffectListContainer->Hide();
1023 mEffectListContainer->GetSizer()->Clear(true);
1024
1025
1026 if(!mDelegate || mDelegate->GetEffectList().GetStatesCount() == 0)
1027 mEffectListContainer->Hide();
1028
1029 auto isEmpty{true};
1030 if(mDelegate)
1031 {
1032 auto& effects = mDelegate->GetEffectList();
1033 isEmpty = effects.GetStatesCount() == 0;
1034 for(size_t i = 0, count = effects.GetStatesCount(); i < count; ++i)
1035 InsertEffectRow(i, effects.GetStateAt(i));
1036 }
1038 //Workaround for GTK: Underlying GTK widget does not update
1039 //its size when wxWindow size is set to zero
1040 mEffectListContainer->Show(!isEmpty);
1041 if(mFooter != nullptr)
1042 mFooter->Show(isEmpty);
1043
1044 SendSizeEventToParent();
1045 }
1046
1047 void OnAddEffectClicked(const wxCommandEvent& event)
1048 {
1049 if(!mDelegate || !mProject)
1050 return;
1051
1052 const auto effectId = EffectsMenuHelper::PickEffect(
1053 *mProject,
1054 dynamic_cast<wxWindow*>(event.GetEventObject()),
1055 {}
1056 );
1057
1058 if(!effectId || effectId->empty())
1059 return;
1060
1061 auto plug = PluginManager::Get().GetPlugin(*effectId);
1062 if(!plug)
1063 return;
1064
1067 XO("This plugin could not be loaded.\nIt may have been deleted."),
1069 .Caption(XO("Plugin Error")));
1070
1071 return;
1072 }
1073
1074 if(const auto state = AudioIO::Get()->AddState(*mProject, mDelegate->GetChannelGroup(), *effectId))
1075 {
1076 auto effect = state->GetEffect();
1077 assert(effect); // postcondition of AddState
1078 const auto effectName = effect->GetName();
1079 ProjectHistory::Get(*mProject).PushState(
1084 XO("Added %s to %s").Format(effectName, mDelegate->GetSourceName()),
1085 //i18n-hint: undo history record
1086 XO("Add %s").Format(effectName));
1087 }
1088 }
1089
1090 void InsertEffectRow(size_t index,
1091 const std::shared_ptr<RealtimeEffectState> &pState)
1092 {
1093 if(!mDelegate || !mProject)
1094 return;
1095
1096 // See comment in ReloadEffectsList
1097 if(!mEffectListContainer->IsShown())
1098 mEffectListContainer->Show();
1099
1101 row->SetBackgroundColorIndex(clrEffectListItemBackground);
1102 row->SetEffect(*mProject, mDelegate, pState);
1103 mEffectListContainer->GetSizer()->Insert(index, row, 0, wxEXPAND);
1104 }
1105};
1106
1107
1109{
1111
1113 : mProject { project }
1114 {}
1115
1116 void UpdatePrefs() override
1117 {
1118 auto& trackList = TrackList::Get(mProject);
1119 for (auto waveTrack : trackList.Any<WaveTrack>())
1121 }
1122};
1123
1124namespace {
1125AttachedWindows::RegisteredFactory sKey{
1126[](AudacityProject &project) -> wxWeakRef<wxWindow> {
1127 constexpr auto EffectsPanelMinWidth { 255 };
1128
1129 const auto pProjectWindow = &ProjectWindow::Get(project);
1131 project,
1132 pProjectWindow->GetContainerWindow(),
1133 wxID_ANY,
1134 wxDefaultPosition,
1135 wxDefaultSize,
1136 wxNO_BORDER | wxSP_LIVE_UPDATE | wxSP_THIN_SASH
1137 );
1138 effectsPanel->SetMinSize({EffectsPanelMinWidth, -1});
1139 effectsPanel->SetName(_("Realtime effects"));
1140 effectsPanel->SetBackgroundColorIndex(clrMedium);
1141 effectsPanel->Hide();//initially hidden
1142 return effectsPanel;
1143}
1144};
1145}
1146
1148{
1150}
1151
1152const RealtimeEffectPanel &
1154{
1155 return Get(const_cast<AudacityProject &>(project));
1156}
1157
1159 AudacityProject& project, wxWindow* parent, wxWindowID id, const wxPoint& pos,
1160 const wxSize& size,
1161 long style, const wxString& name)
1162 : wxSplitterWindow(parent, id, pos, size, style, name)
1163 , mProject(project)
1164 , mPrefsListenerHelper(std::make_unique<PrefsListenerHelper>(project))
1165{
1166 SetSashInvisible();//Use custom sash
1167
1168 SetSashGravity(1.0);
1171 {
1176 std::make_shared<ProjectEffectListDelegate>(mProject)
1177 );
1178 }
1179 SetMinimumPaneSize(mTrackEffectsPanel->GetSizer()->CalcMin().y);
1180 SplitHorizontally(mTrackEffectsPanel, mProjectEffectsPanel, -267);
1181
1182 Bind(wxEVT_CHAR_HOOK, &RealtimeEffectPanel::OnCharHook, this);
1185 auto track = evt.mpTrack.lock();
1186 auto waveTrack = std::dynamic_pointer_cast<WaveTrack>(track);
1187
1188 if (waveTrack == nullptr)
1189 return;
1190
1191 switch (evt.mType)
1192 {
1194 if (mCurrentTrack.lock() == waveTrack)
1195 mTrackTitle->SetLabel(track->GetName());
1196 UpdateRealtimeEffectUIData(*waveTrack);
1197 break;
1199 if (evt.mExtra == 0)
1200 mPotentiallyRemovedTracks.push_back(waveTrack);
1201 break;
1203 // Addition can be fired as a part of "replace" event.
1204 // Calling UpdateRealtimeEffectUIData is mostly no-op,
1205 // it will just create a new State and Access for it.
1206 UpdateRealtimeEffectUIData(*waveTrack);
1207 break;
1208 default:
1209 break;
1210 }
1211 });
1212
1214 [this](UndoRedoMessage message)
1215 {
1216 if (
1217 message.type == UndoRedoMessage::Type::Purge ||
1218 message.type == UndoRedoMessage::Type::BeginPurge ||
1219 message.type == UndoRedoMessage::Type::EndPurge)
1220 return;
1221
1222 auto& trackList = TrackList::Get(mProject);
1223
1224 // Realtime effect UI is only updated on Undo or Redo
1225 auto waveTracks = trackList.Any<WaveTrack>();
1226
1227 if (
1228 message.type == UndoRedoMessage::Type::UndoOrRedo ||
1229 message.type == UndoRedoMessage::Type::Reset)
1230 {
1231 for (auto waveTrack : waveTracks)
1232 UpdateRealtimeEffectUIData(*waveTrack);
1233 }
1234
1235 // But mPotentiallyRemovedTracks processing happens as fast as possible.
1236 // This event is fired right after the track is deleted, so we do not
1237 // hold the strong reference to the track much longer than need.
1238 if (mPotentiallyRemovedTracks.empty())
1239 return;
1240
1241 // Collect RealtimeEffectUIs that are currently shown
1242 // for the potentially removed tracks
1243 std::vector<RealtimeEffectStateUI*> shownUIs;
1244
1245 for (auto track : mPotentiallyRemovedTracks)
1246 {
1247 // By construction, track cannot be null
1248 assert(track != nullptr);
1249
1251 *track,
1252 [&shownUIs](auto& ui)
1253 {
1254 if (ui.IsShown())
1255 shownUIs.push_back(&ui);
1256 });
1257 }
1258
1259 // For every UI shown - check if the corresponding state
1260 // is reachable from the current track list.
1261 for (auto effectUI : shownUIs)
1262 {
1263 bool reachable = false;
1264
1265 for (auto track : waveTracks)
1266 {
1268 *track,
1269 [effectUI, &reachable](auto& ui)
1270 {
1271 if (effectUI == &ui)
1272 reachable = true;
1273 });
1274
1275 if (reachable)
1276 break;
1277 }
1278
1279 if (!reachable)
1280 // Don't need to autosave for an unreachable state
1281 effectUI->Hide();
1282 }
1283
1285 });
1286
1288 .Subscribe([this](const TrackFocusChangeMessage& msg) {
1289 if (IsShown())
1290 {
1291 auto& trackFocus = TrackFocus::Get(mProject);
1292 ShowPanel(dynamic_cast<SampleTrack *>(trackFocus.Get()), false);
1293 }
1294 });
1295
1296 Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent&) {
1297 HidePanel(); });
1298}
1299
1301{
1302}
1303
1305{
1306 if(track == nullptr)
1307 {
1308 ResetTrack();
1309 return;
1310 }
1311
1312 wxWindowUpdateLocker freeze(this);
1313
1315
1316 auto &projectWindow = ProjectWindow::Get(mProject);
1317 const auto pContainerWindow = projectWindow.GetContainerWindow();
1318 if (pContainerWindow->GetWindow1() != this)
1319 {
1320 //Restore previous effects window size
1321 pContainerWindow->SplitVertically(
1322 this,
1323 projectWindow.GetTrackListWindow(),
1324 this->GetSize().GetWidth());
1325 }
1326 if(focus)
1327 SetFocus();
1328 projectWindow.Layout();
1329}
1330
1332{
1333 wxWindowUpdateLocker freeze(this);
1334
1335 auto &projectWindow = ProjectWindow::Get(mProject);
1336 const auto pContainerWindow = projectWindow.GetContainerWindow();
1337 const auto pTrackListWindow = projectWindow.GetTrackListWindow();
1338 if (pContainerWindow->GetWindow2() == nullptr)
1339 //only effects panel is present, restore split positions before removing effects panel
1340 //Workaround: ::Replace and ::Initialize do not work here...
1341 pContainerWindow->SplitVertically(this, pTrackListWindow);
1342
1343 pContainerWindow->Unsplit(this);
1344 pTrackListWindow->SetFocus();
1345 projectWindow.Layout();
1346}
1347
1348void RealtimeEffectPanel::SetTrack(const std::shared_ptr<SampleTrack>& track)
1349{
1350 //Avoid creation-on-demand of a useless, empty list in case the track is of non-wave type.
1351 if(track && dynamic_cast<WaveTrack*>(&*track) != nullptr)
1352 {
1353 mTrackTitle->SetLabel(track->GetName());
1355 track && RealtimeEffectList::Get(*track).IsActive()
1359 mProject,
1360 std::make_shared<TrackEffectListUIDelegate>(track)
1361 );
1362
1363 mCurrentTrack = track;
1364 //i18n-hint: argument - track name
1365 mTrackEffectsHeader->SetName(wxString::Format(_("Realtime effects for %s"), track->GetName()));
1366 }
1367 else
1368 ResetTrack();
1369}
1370
1372{
1373 mTrackTitle->SetLabel(wxEmptyString);
1376 mCurrentTrack.reset();
1377 mTrackEffectsHeader->SetName(wxEmptyString);
1378}
1379
1381{
1382 mTrackEffectsHeader->SetFocus();
1383}
1384
1386{
1387 mTrackEffectsPanel = safenew wxPanel(this);
1388
1389 auto vSizer = std::make_unique<wxBoxSizer>(wxVERTICAL);
1390
1392#if wxUSE_ACCESSIBILITY
1393 safenew WindowAccessible(header);
1394#endif
1395 header->SetBackgroundColorIndex(clrMedium);
1396 {
1397 auto hSizer = std::make_unique<wxBoxSizer>(wxHORIZONTAL);
1398 auto toggleEffects = safenew ThemedAButtonWrapper<AButton>(header);
1399 toggleEffects->SetImageIndices(0, bmpEffectOff, bmpEffectOff, bmpEffectOn, bmpEffectOn, bmpEffectOff);
1400 toggleEffects->SetButtonToggles(true);
1401 toggleEffects->SetTranslatableLabel(XO("Power"));
1402 toggleEffects->SetBackgroundColorIndex(clrMedium);
1403 mToggleTrackEffects = toggleEffects;
1404
1405 toggleEffects->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
1406 if (mTrackEffectList)
1407 {
1409
1412 }
1413 });
1414
1415 hSizer->Add(toggleEffects, 0, wxSTRETCH_NOT | wxALIGN_CENTER | wxLEFT, 5);
1416 {
1417 auto vSizer = std::make_unique<wxBoxSizer>(wxVERTICAL);
1418
1419 auto headerText = safenew ThemedWindowWrapper<wxStaticText>(header, wxID_ANY, wxEmptyString);
1420 headerText->SetFont(wxFont(wxFontInfo().Bold()));
1421 headerText->SetTranslatableLabel(XO("Realtime Effects"));
1422 headerText->SetForegroundColorIndex(clrTrackPanelText);
1423
1424 auto trackTitle = safenew ThemedWindowWrapper<wxStaticText>(header, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxST_ELLIPSIZE_END);
1425 trackTitle->SetForegroundColorIndex(clrTrackPanelText);
1426 mTrackTitle = trackTitle;
1427
1428 vSizer->Add(headerText);
1429 vSizer->Add(trackTitle);
1430
1431 hSizer->Add(vSizer.release(), 1, wxEXPAND | wxALL, 10);
1432 }
1433 auto close = safenew ThemedAButtonWrapper<AButton>(header);
1434 close->SetTranslatableLabel(XO("Close"));
1435 close->SetImageIndices(0, bmpCloseNormal, bmpCloseHover, bmpCloseDown, bmpCloseHover, bmpCloseDisabled);
1436 close->SetBackgroundColorIndex(clrMedium);
1437
1438 close->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { Close(); });
1439
1440 hSizer->Add(close, 0, wxSTRETCH_NOT | wxALIGN_CENTER | wxRIGHT, 5);
1441
1442 header->SetSizer(hSizer.release());
1443 }
1444 vSizer->Add(header, 0, wxEXPAND);
1445
1447 effectList->SetBackgroundColorIndex(clrMedium);
1448 {
1449 auto footer = safenew ThemedWindowWrapper<wxPanel>(effectList, wxID_ANY);
1450 footer->SetBackgroundColorIndex(clrMedium);
1451
1452 auto addEffectHint = safenew ThemedWindowWrapper<wxStaticText>(footer, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE);
1453 //Workaround: text is set in the OnSizeChange
1454 addEffectHint->SetForegroundColorIndex(clrTrackPanelText);
1455
1456 auto addEffectTutorialLink = safenew ThemedWindowWrapper<wxHyperlinkCtrl>(
1457 footer, wxID_ANY, _("Watch video"),
1458 "https://www.audacityteam.org/realtime-video", wxDefaultPosition,
1459 wxDefaultSize, wxHL_ALIGN_LEFT | wxHL_CONTEXTMENU);
1460
1461 addEffectTutorialLink->Bind(
1462 wxEVT_HYPERLINK, [](wxHyperlinkEvent& event)
1463 { BasicUI::OpenInDefaultBrowser(event.GetURL()); });
1464
1465 auto footerSizer = std::make_unique<wxBoxSizer>(wxVERTICAL);
1466 footerSizer->Add(addEffectHint, 0, wxLEFT | wxRIGHT | wxBOTTOM | wxEXPAND, 20);
1467 footerSizer->Add(addEffectTutorialLink, 0, wxLEFT | wxRIGHT | wxEXPAND, 20);
1468 footer->SetSizer(footerSizer.release());
1469
1470 footer->Bind(wxEVT_SIZE, [=](wxSizeEvent& event)
1471 {
1472 if(auto sizerItem = footer->GetSizer()->GetItem(addEffectHint))
1473 {
1474 //We need to wrap the text whenever panel width changes and adjust widget height
1475 //so that text is fully visible, but there is no height-for-width layout algorithm
1476 //in wxWidgets yet, so for now we just do it manually
1477
1478 //Restore original text, because 'Wrap' will replace it with wrapped one
1479 addEffectHint->SetLabel(_("Realtime effects are non-destructive and can be changed at any time."));
1480 addEffectHint->Wrap(mTrackEffectsPanel->GetClientSize().x - sizerItem->GetBorder() * 2);
1481 addEffectHint->InvalidateBestSize();
1482 }
1483 event.Skip();
1484 });
1485
1486 effectList->SetFooter(footer);
1487 }
1488 vSizer->Add(effectList, 1, wxEXPAND);
1489
1490 mTrackEffectsHeader = header;
1491 mTrackEffectList = effectList;
1492
1493 mTrackEffectsPanel->SetSizer(vSizer.release());
1494}
1495
1496class SashLine : public wxWindow
1497{
1498 wxWeakRef<wxSplitterWindow> mSplitter;
1499 bool mDrag {false};
1500public:
1501 SashLine(wxWindow *parent,
1502 wxWindowID id,
1503 const wxPoint& pos = wxDefaultPosition,
1504 const wxSize& size = wxDefaultSize)
1505 : wxWindow(parent, id, pos, size, wxNO_BORDER, wxEmptyString)
1506 {
1507 wxWindow::SetBackgroundStyle(wxBG_STYLE_PAINT);
1508 SetCursor(wxCursor(wxCURSOR_SIZENS));
1509
1510 Bind(wxEVT_LEFT_DOWN, &SashLine::OnMouseDown, this);
1511 Bind(wxEVT_LEFT_UP, &SashLine::OnMouseUp, this);
1512 Bind(wxEVT_MOTION, &SashLine::OnMove, this);
1513 Bind(wxEVT_MOUSE_CAPTURE_LOST, &SashLine::OnMouseCaptureLost, this);
1514 Bind(wxEVT_PAINT, &SashLine::OnPaint, this);
1515 }
1516
1517 void SetSplitterWindow(wxSplitterWindow* window)
1518 {
1519 mSplitter = window;
1520 }
1521
1522 bool AcceptsFocus() const override { return false; }
1523
1524private:
1525
1526 void OnMouseCaptureLost(wxMouseCaptureLostEvent& event)
1527 {
1528 mDrag = false;
1529 }
1530
1531 void OnMouseDown(wxMouseEvent& evt)
1532 {
1533 if(!mSplitter)
1534 return;
1535 CaptureMouse();
1536 mDrag = true;
1537 }
1538
1539 void OnMouseUp(wxMouseEvent& evt)
1540 {
1541 mDrag = false;
1542 ReleaseMouse();
1543 }
1544
1545 void OnMove(wxMouseEvent& evt)
1546 {
1547 if(!mDrag || !mSplitter)
1548 {
1549 evt.Skip();
1550 return;
1551 }
1552 const auto pos = mSplitter->ScreenToClient(ClientToScreen(evt.GetPosition()));
1553 mSplitter->SetSashPosition(
1554 std::clamp(
1555 pos.y,
1556 mSplitter->GetMinimumPaneSize(),
1557 mSplitter->GetSize().y - mSplitter->GetMinimumPaneSize()
1558 ));
1559 }
1560
1561 void OnPaint(wxPaintEvent&)
1562 {
1563 wxBufferedPaintDC dc(this);
1564 const auto rect = wxRect(GetSize());
1565
1566 dc.SetPen(*wxTRANSPARENT_PEN);
1567 dc.SetBrush(GetBackgroundColour());
1568 dc.DrawRectangle(rect);
1569
1570 dc.SetPen(GetForegroundColour());
1571 dc.SetBrush(*wxTRANSPARENT_BRUSH);
1572 const auto yy = rect.GetTop() + rect.GetHeight() / 2;
1573 dc.DrawLine(rect.GetLeft(), yy, rect.GetRight(), yy);
1574 }
1575};
1576
1578{
1579 mProjectEffectsPanel = safenew wxPanel(this);
1580
1581 auto vSizer = std::make_unique<wxBoxSizer>(wxVERTICAL);
1582
1583 const auto sash = safenew ThemedWindowWrapper<SashLine>(mProjectEffectsPanel, wxID_ANY);
1584 sash->SetMinSize(wxSize{-1, 3});
1585 sash->SetSplitterWindow(this);
1586 sash->SetBackgroundColorIndex(clrMedium);
1587 sash->SetForegroundColorIndex(clrDark);
1588 vSizer->Add(sash, 0, wxEXPAND);
1589
1591#if wxUSE_ACCESSIBILITY
1592 safenew WindowAccessible(header);
1593#endif
1594 header->SetBackgroundColorIndex(clrMedium);
1595 {
1596 auto hSizer = std::make_unique<wxBoxSizer>(wxHORIZONTAL);
1597 auto toggleEffects = safenew ThemedAButtonWrapper<AButton>(header);
1598 toggleEffects->SetImageIndices(0, bmpEffectOff, bmpEffectOff, bmpEffectOn, bmpEffectOn, bmpEffectOff);
1599 toggleEffects->SetButtonToggles(true);
1600 toggleEffects->SetTranslatableLabel(XO("Power"));
1601 toggleEffects->SetBackgroundColorIndex(clrMedium);
1602 mToggleMasterEffects = toggleEffects;
1603
1604 toggleEffects->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
1606 {
1608
1611 }
1612 });
1613
1614 hSizer->Add(toggleEffects, 0, wxSTRETCH_NOT | wxALIGN_CENTER | wxLEFT, 5);
1615 {
1616 auto vSizer = std::make_unique<wxBoxSizer>(wxVERTICAL);
1617
1618 auto headerText = safenew ThemedWindowWrapper<wxStaticText>(header, wxID_ANY, wxEmptyString);
1619 headerText->SetFont(wxFont(wxFontInfo().Bold()));
1620 headerText->SetTranslatableLabel(XO("Master Effects"));
1621 headerText->SetForegroundColorIndex(clrTrackPanelText);
1622 header->SetName(headerText->GetLabel());
1623
1624 auto desc = safenew ThemedWindowWrapper<wxStaticText>(header, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxST_ELLIPSIZE_END);
1625 desc->SetForegroundColorIndex(clrTrackPanelText);
1626 desc->SetTranslatableLabel(XO("Applies to all tracks"));
1627
1628 vSizer->Add(headerText);
1629 vSizer->Add(desc);
1630
1631 hSizer->Add(vSizer.release(), 1, wxEXPAND | wxALL, 10);
1632 }
1633
1634 header->SetSizer(hSizer.release());
1635 }
1636 vSizer->Add(header, 0, wxEXPAND | wxTOP, 5);
1637
1639 effectList->SetBackgroundColorIndex(clrMedium);
1640 vSizer->Add(effectList, 1, wxEXPAND);
1641
1642 mMasterEffectList = effectList;
1643
1644 mProjectEffectsPanel->SetSizer(vSizer.release());
1645}
1646
1648{
1649 if(evt.GetKeyCode() == WXK_ESCAPE && IsShown() && IsDescendant(FindFocus()))
1650 Close();
1651 else
1652 evt.Skip();
1653}
Toolkit-neutral facade for basic user interface services.
wxString PluginID
const TranslatableString name
Definition: Distortion.cpp:76
@ EffectTypeAnalyze
@ EffectTypeProcess
ChoiceSetting RealtimeEffectsGroupBy
XO("Cut/Copy/Paste")
#define _(s)
Definition: Internat.h:73
#define safenew
Definition: MemoryX.h:10
AUDACITY_DLL_API AttachedWindows & GetAttachedWindows(AudacityProject &project)
accessors for certain important windows associated with each project
RealtimeEffectStateChange
for(int ii=0, nn=names.size();ii< nn;++ii)
TranslatableString label
Definition: TagsEditor.cpp:165
const auto project
THEME_API Theme theTheme
Definition: Theme.cpp:82
int id
static std::once_flag flag
A wxButton with mouse-over behaviour.
Definition: AButton.h:104
void PushDown()
Definition: AButton.cpp:644
bool IsDown()
Definition: AButton.h:226
void Disable()
Definition: AButton.cpp:627
void Enable()
Definition: AButton.cpp:618
void PopUp()
Definition: AButton.cpp:652
@ TextButton
Definition: AButton.h:112
void SetEnabled(bool state)
Definition: AButton.h:200
static void DrawFocus(wxDC &dc, wxRect &r)
Definition: AColor.cpp:247
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
static AudioIO * Get()
Definition: AudioIO.cpp:126
void RemoveState(AudacityProject &project, ChannelGroup *pGroup, std::shared_ptr< RealtimeEffectState > pState)
Forwards to RealtimeEffectManager::RemoveState with proper init scope.
Definition: AudioIO.cpp:369
wxString Read() const
Definition: Prefs.cpp:388
Subclass & Get(const RegisteredFactory &key)
Get reference to an attachment, creating on demand if not present, down-cast it to Subclass.
Definition: ClientData.h:318
EffectPlugin * GetEffect(const PluginID &ID)
static EffectManager & Get()
Abstract base class used in importing a file.
An explicitly nonlocalized string, not meant for the user to see.
Definition: Identifier.h:22
Changes default arrow navigation to behave more list- or table-like. Instead of searching focusable i...
int GetSourceIndex() const noexcept
int GetTargetIndex() const noexcept
void Create(wxWindow *parent, wxWindowID id, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=0, const wxString &name=wxPanelNameStr)
Subscription Subscribe(Callback callback)
Connect a callback to the Publisher; later-connected are called earlier.
Definition: Observer.h:199
A move-only handle representing a connection to a Publisher.
Definition: Observer.h:70
void Reset() noexcept
Breaks the connection (constant time)
Definition: Observer.cpp:101
static PendingTracks & Get(AudacityProject &project)
const wxString & GetVendor() const
static Identifier GetEffectNameFromID(const PluginID &ID)
static bool IsPluginAvailable(const PluginDescriptor &plug)
const PluginDescriptor * GetPlugin(const PluginID &ID) const
static PluginManager & Get()
A listener notified of changes in preferences.
Definition: Prefs.h:652
RealtimeEffectList & GetEffectList() override
wxString GetSourceName() override
ProjectEffectListDelegate(AudacityProject &project)
std::shared_ptr< RealtimeEffectList > mEffectList
ChannelGroup * GetChannelGroup() override
void PushState(const TranslatableString &desc, const TranslatableString &shortDesc)
void ModifyState(bool bWantsAutoSave)
static ProjectHistory & Get(AudacityProject &project)
static ProjectWindow & Get(AudacityProject &project)
void Visit(const StateVisitor &func)
Apply the function to all states sequentially.
static RealtimeEffectList & Get(AudacityProject &project)
bool IsActive() const
Non-blocking atomic boolean load.
void OnEffectListItemChange(const RealtimeEffectListMessage &msg)
RealtimeEffectListWindow(wxWindow *parent, wxWindowID winid=wxID_ANY, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=wxScrolledWindowStyle, const wxString &name=wxPanelNameStr)
void OnAddEffectClicked(const wxCommandEvent &event)
void SetDelegate(AudacityProject &project, const std::shared_ptr< EffectListUIDelegate > &delegate)
std::shared_ptr< EffectListUIDelegate > mDelegate
Observer::Subscription mEffectListItemMovedSubscription
void InsertEffectRow(size_t index, const std::shared_ptr< RealtimeEffectState > &pState)
void SetFooter(wxWindow *footer)
wxWeakRef< AudacityProject > mProject
static RealtimeEffectManager & Get(AudacityProject &project)
UI Panel that displays realtime effects from the effect stack of an individual track,...
RealtimeEffectListWindow * mTrackEffectList
Observer::Subscription mUndoSubscription
wxStaticText * mTrackTitle
void ShowPanel(SampleTrack *track, bool focus)
void OnCharHook(wxKeyEvent &evt)
std::weak_ptr< SampleTrack > mCurrentTrack
static RealtimeEffectPanel & Get(AudacityProject &project)
RealtimeEffectListWindow * mMasterEffectList
std::vector< std::shared_ptr< SampleTrack > > mPotentiallyRemovedTracks
RealtimeEffectPanel(AudacityProject &project, wxWindow *parent, wxWindowID id, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=0, const wxString &name=wxPanelNameStr)
Observer::Subscription mFocusChangeSubscription
AudacityProject & mProject
Observer::Subscription mTrackListChanged
void SetTrack(const std::shared_ptr< SampleTrack > &track)
Shows effects from the effect stack of the track.
const PluginID & GetID() const noexcept
static RealtimeEffectStateUI & Get(RealtimeEffectState &state)
void OnPaint(wxPaintEvent &)
wxWeakRef< wxSplitterWindow > mSplitter
void SetSplitterWindow(wxSplitterWindow *window)
bool AcceptsFocus() const override
void OnMouseCaptureLost(wxMouseCaptureLostEvent &event)
void OnMove(wxMouseEvent &evt)
SashLine(wxWindow *parent, wxWindowID id, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize)
void OnMouseUp(wxMouseEvent &evt)
void OnMouseDown(wxMouseEvent &evt)
wxColour & Colour(int iIndex)
wxBitmap & Bitmap(int iIndex)
wxString GetSourceName() override
TrackEffectListUIDelegate(std::shared_ptr< SampleTrack > track)
std::shared_ptr< SampleTrack > mTrack
RealtimeEffectList & GetEffectList() override
ChannelGroup * GetChannelGroup() override
Track * Get()
Definition: TrackFocus.cpp:156
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:110
std::shared_ptr< Subclass > SharedPointer()
Definition: Track.h:146
const wxString & GetName() const
Name is always the same for all channels of a group.
Definition: Track.cpp:64
auto Any() -> TrackIterRange< TrackType >
Definition: Track.h:950
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:314
Holds a msgid for the translation catalog; may also bind format arguments.
wxString Translation() const
static UndoManager & Get(AudacityProject &project)
Definition: UndoManager.cpp:71
void MarkUnsaved()
A Track that contains audio waveform data.
Definition: WaveTrack.h:203
An alternative to using wxWindowAccessible, which in wxWidgets 3.1.1 contained GetParent() which was ...
DropHintLine(wxWindow *parent, wxWindowID id, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize)
static std::optional< wxString > PickEffect(AudacityProject &project, wxWindow *parent, const wxString &selectedEffectID)
void Populate(AudacityProject &project, Visitor< Traits > &visitor)
RealtimeEffectControl(wxWindow *parent, wxWindowID winid, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize)
void SetEffect(AudacityProject &project, const std::shared_ptr< EffectListUIDelegate > &delegate, const std::shared_ptr< RealtimeEffectState > &pState)
void Create(wxWindow *parent, wxWindowID winid, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize)
bool OpenInDefaultBrowser(const wxString &url)
Open an URL in default browser.
Definition: BasicUI.cpp:246
Services * Get()
Fetch the global instance, or nullptr if none is yet installed.
Definition: BasicUI.cpp:202
MessageBoxResult ShowMessageBox(const TranslatableString &message, MessageBoxOptions options={})
Show a modal message box with either Ok or Yes and No, and optionally Cancel.
Definition: BasicUI.h:287
std::unique_ptr< WindowPlacement > FindFocus()
Find the window that is accepting keyboard input, if any.
Definition: BasicUI.h:383
void PopulateEffectsMenu(Group &menuItems, EffectType type, CommandFlag batchflags, const wxString &groupby, void(*onMenuCommand)(const CommandContext &), std::function< bool(const PluginDescriptor &)> pred={})
Definition: MenuHelper.cpp:563
constexpr auto Section
Definition: MenuRegistry.h:436
constexpr auto Menu
Items will appear in a main toolbar menu or in a sub-menu.
Definition: MenuRegistry.h:445
void VisitWithFunctions(const VisitorFunctions< RegistryTraits > &visitors, const GroupItem< RegistryTraits > *pTopItem, const GroupItem< RegistryTraits > *pRegistry={}, typename RegistryTraits::ComputedItemContextType &computedItemContext=RegistryTraits::ComputedItemContextType::Instance)
Definition: Registry.h:623
const TranslatableString desc
Definition: ExportPCM.cpp:51
void UpdateRealtimeEffectUIData(const AudacityProject &project)
static wxString GetSafeVendor(const PluginDescriptor &descriptor)
void VisitRealtimeEffectStateUIs(const Track &track, Visitor &&visitor)
TranslatableString GetEffectName(RealtimeEffectState &state)
const PluginDescriptor * GetPlugin(const PluginID &ID)
void ReopenRealtimeEffectUIData(AudacityProject &project, SampleTrack &track)
STL namespace.
const TranslatableString label_in
Definition: MenuRegistry.h:321
const auto & GetTitle() const
Definition: MenuRegistry.h:226
@ Remove
Effect item was removed from the list at srcIndex position. affectedState is removed state.
@ DidReplace
Effect item was replaced with a new item at srcIndex position. affectedState is an old state.
@ Move
Item position has changed, from srcIndex to dstIndex. affectedState is the moved state.
@ Insert
New effect item was added to the list at srcIndex position. affectedState is a new state.
@ WillReplace
Effect item will be replaced with a new item at srcIndex position. affectedState is the state to be r...
std::shared_ptr< RealtimeEffectState > affectedState
const Identifier name
Definition: Registry.h:86
Notification of changes in individual tracks of TrackList, or of TrackList's composition.
Definition: Track.h:803
const int mExtra
Definition: Track.h:839
const std::weak_ptr< Track > mpTrack
Definition: Track.h:838
const Type mType
Definition: Track.h:837
@ DELETION
Posted when a track has been deleted from a tracklist. Also posted when one track replaces another.
Definition: Track.h:825
@ ADDITION
Posted when a track has been added to a tracklist. Also posted when one track replaces another.
Definition: Track.h:819
@ TRACK_DATA_CHANGE
Posted when certain fields of a track change.
Definition: Track.h:809
Type of message published by UndoManager.
Definition: UndoManager.h:55
enum UndoRedoMessage::Type type