Audacity 3.2.0
PluginRegistrationDialog.cpp
Go to the documentation of this file.
1/*!*********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 @file PluginRegistrationDialog.cpp
6
7 Paul Licameli split from PluginManager.cpp
8
9**********************************************************************/
11
12#include <numeric>
13#include <unordered_map>
14
15#include "AudacityMessageBox.h"
16#include "EffectInterface.h"
17#include "HelpSystem.h"
19#include "ModuleManager.h"
20#include "PluginManager.h"
22#include "ProgressDialog.h"
23#include "ShuttleGui.h"
24
25#include <set>
26#include <wx/setup.h> // for wxUSE_* macros
27#include <wx/app.h>
28#include <wx/defs.h>
29#include <wx/dynlib.h>
30#include <wx/filename.h>
31#include <wx/wfstream.h>
32#include <wx/utils.h>
33#include <wx/dc.h>
34#include <wx/sizer.h>
35#include <wx/vlbox.h>
36#include <wx/choice.h>
37
38#include "PluginDataModel.h"
39#include "PluginDataViewCtrl.h"
40
41enum
42{
49};
50
60
62 XO("All"),
63 XO("Disabled"),
64 XO("Enabled")
65};
66
67static const std::vector<std::pair<int, TranslatableString>> CategoryFilterValues {
68 { -1, XO("All") },
69 { EffectTypeGenerate, XO("Generator") },
70 { EffectTypeProcess, XO("Effect") },
71 { EffectTypeAnalyze, XO("Analyzer") },
72 { EffectTypeTool, XO("Tool") }
73};
74
75PluginRegistrationDialog::PluginRegistrationDialog(wxWindow *parent, int defaultEffectCategory)
76: wxDialogWrapper(parent,
77 wxID_ANY,
78 XO("Manage Plugins"),
79 wxDefaultPosition, wxDefaultSize,
80 wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
81{
82 mPluginsModel = safenew PluginDataModel(defaultEffectCategory);
83 SetName();
84 Populate();
85}
86
88{
89 //------------------------- Main section --------------------
92 // ----------------------- End of main section --------------
93}
94
97{
98 constexpr int Margin = 12;
99
100 static const std::unordered_map<TranslatableString, TranslatableString> extraProviders = {
101 { XO("Builtin Effects"), XO("Native Audacity") }
102 };
103
104 TranslatableStrings pluginProviderNames;
105 pluginProviderNames.push_back(XO("All"));
106 mPluginProviderIDs.clear();
107 mPluginProviderIDs.push_back({});
108 auto& moduleManager = ModuleManager::Get();
109 for(auto& [name, provider] : moduleManager.Providers())
110 {
111 //Use same name as in prefs
112 auto familySymbol = provider->GetOptionalFamilySymbol();
113 if(!familySymbol.empty())
114 pluginProviderNames.push_back(familySymbol.Msgid());
115 else
116 {
117 auto it = extraProviders.find(provider->GetSymbol().Msgid());
118 if(it != extraProviders.end())
119 pluginProviderNames.push_back(it->second);
120 else
121 continue;
122 }
123 mPluginProviderIDs.push_back(PluginManager::GetID(provider.get()));
124 }
125
126 S.Prop(1).StartPanel(wxEXPAND);
127 {
128 S.StartVerticalLay(true);
129 {
130 S.AddSpace(1, Margin);
131 S.StartHorizontalLay(wxEXPAND, 0);
132 {
133 S.AddSpace(Margin, 1);
134 S.StartHorizontalLay(wxALIGN_LEFT, 0);
135 {
136 TranslatableStrings categoryFilterNames;
137 std::transform(
138 CategoryFilterValues.begin(),
140 std::back_inserter(categoryFilterNames),
141 [](const auto& p) { return p.second; });
142 const auto selectedCategory =
143 std::distance(
144 CategoryFilterValues.begin(),
145 std::find_if(
146 CategoryFilterValues.begin(),
148 [category = mPluginsModel->GetFilterCategory()](const auto& p) {
149 return p.first == category;
150 }
151 ));
152 S
153 .Id(ID_FilterState)
154 .AddChoice(XXO("&Show:"), ShowFilterValues, 0)
155 ->SetMinSize(wxSize(120, -1));
156 S
157 .Id(ID_FilterType)
158 .AddChoice(XXO("&Type:"), pluginProviderNames, 0)
159 ->SetMinSize(wxSize(120, -1));
160 S
162 .AddChoice(XXO("C&ategory:"), categoryFilterNames, selectedCategory)
163 ->SetMinSize(wxSize(120, -1));
164 }
165 S.EndHorizontalLay();
166 S.AddSpace(1, 1, 1);
167 S.StartHorizontalLay(wxALIGN_CENTRE, 0);
168 {
169 const auto searchCtrl = S
170 .MinSize({240, -1})
171 .AddTextBox(XXO("Searc&h:"), wxEmptyString, 0);
172 if(searchCtrl != nullptr)
173 searchCtrl->Bind(wxEVT_TEXT, &PluginRegistrationDialog::OnSearchTextChanged, this);
174 }
175 S.EndHorizontalLay();
176 S.AddSpace(Margin, 1);
177 }
178 S.EndHorizontalLay();
179
180 {
181 const auto pluginsList = safenew PluginDataViewCtrl(
182 S.GetParent(), ID_List, wxDefaultPosition, wxDefaultSize,
183 wxDV_MULTIPLE | wxDV_HORIZ_RULES | wxDV_VERT_RULES,
184 wxDefaultValidator,
185 _("Plugin")
186 );
187 mPluginList = pluginsList;
188 mPluginList->AssociateModel(mPluginsModel.get());
189 mPluginList->SetMinSize({728, 288});
190 mPluginList->GetColumn(PluginDataModel::ColumnName)->SetSortOrder(true);
191 mPluginsModel->Resort();
192 }
193
194 S.SetBorder(Margin);
195 S.Id(ID_List)
196 .Prop(1)
197 .AddWindow(mPluginList, wxEXPAND);
198 S.SetBorder(2);
199
200 S.AddSpace(1, Margin);
201
202 S.StartHorizontalLay(wxALIGN_LEFT | wxEXPAND, 0);
203 {
204 S.AddSpace(Margin, 1);
205 S.Id(ID_Rescan).AddButton(XXO("&Rescan"));
206#if defined(__WXMSW__) || defined(__WXMAC__)
207 S.Id(ID_GetMoreEffects).AddButton(XXO("&Get more effects..."));
208#endif
209 S.AddSpace(1, 1, 1);
210
211 S.Id(wxID_OK).AddButton(XXO("&OK"));
212 S.Id(wxID_CANCEL).AddButton(XXO("&Cancel"));
213 S.AddSpace(Margin, 1);
214 }
215 S.EndHorizontalLay();
216 S.AddSpace(1, Margin);
217 }
218 S.EndVerticalLay();
219 }
220 S.EndStatic();
221
222 wxRect r = wxGetClientDisplayRect();
223
224 Layout();
225 Fit();
226
227 wxSize sz = GetSize();
228 sz.SetWidth(wxMin(sz.GetWidth(), r.GetWidth()));
229 sz.SetHeight(wxMin(sz.GetHeight(), r.GetHeight()));
230 SetMinSize(sz);
231
232 mPluginList->GetColumn(PluginDataModel::ColumnName)->SetWidth(200);
233 mPluginList->GetColumn(PluginDataModel::ColumnType)->SetWidth(80);
234 mPluginList->GetColumn(PluginDataModel::ColumnPath)->SetWidth(350);
235
236 // Parent window is usually not there yet, so centre on screen rather than on parent.
237 CenterOnScreen();
238
239}
240
242{
244 mPluginsModel->GetFilterCategory(),
245 mPluginsModel->GetFilterState(),
246 mPluginsModel->GetFilterType(),
247 mPluginsModel->GetFilterExpr()
248 ));
249 mPluginList->AssociateModel(mPluginsModel.get());
250}
251
253{
254 mPluginsModel->SetFilterExpr(evt.GetString().Trim().Trim(true));
255}
256
258{
259 const auto index = evt.GetInt();
260
261 mPluginsModel->SetFilterState(
262 index == 2 ? 1 : (index == 1 ? 0 : -1)
263 );
264}
265
267{
268 const auto index = evt.GetInt();
269 if(index >= 0 && index < mPluginProviderIDs.size())
270 mPluginsModel->SetFilterType(mPluginProviderIDs[index]);
271}
272
274{
275 const auto index = evt.GetInt();
276 if(index >= 0 && index < CategoryFilterValues.size())
277 mPluginsModel->SetFilterCategory(CategoryFilterValues[index].first);
278}
279
280void PluginRegistrationDialog::OnRescan(wxCommandEvent& WXUNUSED(evt))
281{
282 wxTheApp->CallAfter([self = wxWeakRef(this)] {
283 std::set<PluginPath> disabledPlugins;
284 std::vector<wxString> failedPlugins;
285
286 auto& pm = PluginManager::Get();
287
288 // Record list of plugins that are currently disabled
289 for (auto& plug : pm.AllPlugins())
290 {
291 PluginType plugType = plug.GetPluginType();
292 if (plugType != PluginTypeEffect && plugType != PluginTypeStub)
293 continue;
294
295 if (!plug.IsEnabled())
296 disabledPlugins.insert(plug.GetPath());
297 }
298
299 //PluginManager::ClearEffectPlugins() removes all effects
300 //making all pointers cached in PluginDataModel invalid.
301 //Reset model pointer before clearing effects so that
302 //nothing attempts to access it.
303 if(self)
304 self->mPluginList->AssociateModel(nullptr);
305
306 pm.ClearEffectPlugins();
307
308 auto newPlugins = PluginManager::Get().CheckPluginUpdates();
309 if (!newPlugins.empty())
310 {
311 PluginStartupRegistration reg(newPlugins);
312 reg.Run();
313
314 failedPlugins = reg.GetFailedPluginsPaths();
315 }
316
317 // Disable all plugins which were previously disabled
318 for (auto& plug : pm.AllPlugins())
319 {
320 PluginType plugType = plug.GetPluginType();
321 if (plugType != PluginTypeEffect && plugType != PluginTypeStub)
322 continue;
323
324 const auto& path = plug.GetPath();
325 if (disabledPlugins.find(path) != disabledPlugins.end())
326 plug.SetEnabled(false);
327 }
328
329 pm.Save();
330 pm.NotifyPluginsChanged();
331
332 if(self)
333 {
334 self->ReloadModel();
335 if (!failedPlugins.empty())
336 {
337 auto dialog = safenew IncompatiblePluginsDialog(self.get(), wxID_ANY, ScanType::Manual, failedPlugins);
338 dialog->ShowModal();
339 self->Refresh();
340 }
341 }
342 });
343}
344
345void PluginRegistrationDialog::OnGetMoreEffects(wxCommandEvent& WXUNUSED(evt))
346{
347 OpenInDefaultBrowser("https://www.musehub.com");
348}
349
350void PluginRegistrationDialog::OnOK(wxCommandEvent & WXUNUSED(evt))
351{
352 auto result = ProgressResult::Success;
353 {
354 // Make sure the progress dialog is deleted before we call EndModal() or
355 // we will leave the project window in an unusable state on OSX.
356 // See bug #1192.
357 std::unique_ptr<ProgressDialog> dialog;
358 wxArrayString last3;
359 auto updateProgress = [&](int num, int denom, const wxString& msg)
360 {
361 last3.insert(last3.begin(), msg);
362 if(last3.size() > 3)
363 last3.pop_back();
364 if(!dialog)
365 {
366 dialog = std::make_unique<ProgressDialog>(
367 Verbatim( GetTitle() ),
370 );
371 dialog->CenterOnParent();
372 }
373 result = dialog->Update(
374 num,
375 denom,
376 TranslatableString(wxJoin(last3, '\n'), {})
377 );
378 return result == ProgressResult::Success;
379 };
380 auto onError = [](const TranslatableString& error) {
381 AudacityMessageBox(error);
382 };
383
384 mPluginsModel->ApplyChanges(updateProgress, onError);
385 }
386 if(result == ProgressResult::Success)
387 EndModal(wxID_OK);
388 else
389 ReloadModel();
390
391}
392
393void PluginRegistrationDialog::OnCancel(wxCommandEvent & WXUNUSED(evt))
394{
395 EndModal(wxID_CANCEL);
396}
int AudacityMessageBox(const TranslatableString &message, const TranslatableString &caption, long style, wxWindow *parent, int x, int y)
END_EVENT_TABLE()
EVT_BUTTON(wxID_NO, DependencyDialog::OnNo) EVT_BUTTON(wxID_YES
@ EffectTypeAnalyze
@ EffectTypeGenerate
@ EffectTypeTool
@ EffectTypeProcess
XO("Cut/Copy/Paste")
XXO("&Cut/Copy/Paste Toolbar")
#define _(s)
Definition: Internat.h:73
#define safenew
Definition: MemoryX.h:10
PluginType
@ PluginTypeStub
@ PluginTypeEffect
static const std::vector< std::pair< int, TranslatableString > > CategoryFilterValues
static const TranslatableStrings ShowFilterValues
@ pdlgHideStopButton
@ eIsCreating
Definition: ShuttleGui.h:37
wxString name
Definition: TagsEditor.cpp:166
#define S(N)
Definition: ToChars.cpp:64
TranslatableString Verbatim(wxString str)
Require calls to the one-argument constructor to go through this distinct global function name.
std::vector< TranslatableString > TranslatableStrings
static ModuleManager & Get()
!brief A plugins list model that can be attached to wxDataViewCtrl
static PluginID GetID(const PluginProvider *provider)
std::map< wxString, std::vector< wxString > > CheckPluginUpdates()
Ensures that all currently registered plugins still exist and scans for new ones.
static PluginManager & Get()
void PopulateOrExchange(ShuttleGui &S)
Defines the dialog and does data exchange with it.
void OnStateFilterValueChanged(wxCommandEvent &evt)
void OnCategoryFilterValueChanged(wxCommandEvent &evt)
wxObjectDataPtr< PluginDataModel > mPluginsModel
void OnTypeFilterValueChanged(wxCommandEvent &evt)
void OnGetMoreEffects(wxCommandEvent &evt)
void OnCancel(wxCommandEvent &evt)
PluginRegistrationDialog(wxWindow *parent, int defaultEffectCategory=-1)
void OnRescan(wxCommandEvent &evt)
void OnOK(wxCommandEvent &evt)
void OnSearchTextChanged(wxCommandEvent &evt)
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:640
Holds a msgid for the translation catalog; may also bind format arguments.
bool OpenInDefaultBrowser(const wxString &url)
Open an URL in default browser.
Definition: BasicUI.cpp:246
static constexpr auto Margin
BuiltinCommandsModule::Registration< CompareAudioCommand > reg