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