From 8fda526577737ce6259af380194f4e606f238cf1 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Fri, 18 Jun 2021 13:08:36 -0400 Subject: [PATCH 01/10] Simplify memory management in PluginManager --- include/audacity/ModuleInterface.h | 7 ++-- modules/mod-nyq-bench/NyqBench.cpp | 8 +++-- src/ModuleManager.cpp | 26 ++++---------- src/ModuleManager.h | 7 ++-- src/PluginManager.cpp | 42 ++++++++-------------- src/PluginManager.h | 11 +++--- src/commands/LoadCommands.cpp | 14 ++------ src/commands/LoadCommands.h | 4 +-- src/effects/EffectManager.cpp | 10 +++--- src/effects/EffectManager.h | 8 ++--- src/effects/LoadEffects.cpp | 14 ++------ src/effects/LoadEffects.h | 4 +-- src/effects/VST/VSTEffect.cpp | 13 ++----- src/effects/VST/VSTEffect.h | 4 +-- src/effects/audiounits/AudioUnitEffect.cpp | 22 +++--------- src/effects/audiounits/AudioUnitEffect.h | 4 +-- src/effects/ladspa/LadspaEffect.cpp | 14 ++------ src/effects/ladspa/LadspaEffect.h | 4 +-- src/effects/lv2/LoadLV2.cpp | 21 +++-------- src/effects/lv2/LoadLV2.h | 4 +-- src/effects/nyquist/LoadNyquist.cpp | 18 +++------- src/effects/nyquist/LoadNyquist.h | 4 +-- src/effects/vamp/LoadVamp.cpp | 21 +++-------- src/effects/vamp/LoadVamp.h | 4 +-- 24 files changed, 96 insertions(+), 192 deletions(-) diff --git a/include/audacity/ModuleInterface.h b/include/audacity/ModuleInterface.h index fdb3b6fb5..5280c70f1 100644 --- a/include/audacity/ModuleInterface.h +++ b/include/audacity/ModuleInterface.h @@ -43,6 +43,7 @@ #define __AUDACITY_MODULEINTERFACE_H__ #include +#include #include "Identifier.h" #include "audacity/ComponentInterface.h" #include "audacity/PluginInterface.h" @@ -129,10 +130,8 @@ public: virtual bool IsPluginValid(const PluginPath & path, bool bFast) = 0; // When appropriate, CreateInstance() will be called to instantiate the plugin. - virtual ComponentInterface *CreateInstance(const PluginPath & path) = 0; - - // When appropriate, DeleteInstance() will be called to delete the plugin. - virtual void DeleteInstance(ComponentInterface *instance) = 0; + virtual std::unique_ptr + CreateInstance(const PluginPath & path) = 0; }; // ---------------------------------------------------------------------------- diff --git a/modules/mod-nyq-bench/NyqBench.cpp b/modules/mod-nyq-bench/NyqBench.cpp index 6fe612e32..b0909b2c6 100755 --- a/modules/mod-nyq-bench/NyqBench.cpp +++ b/modules/mod-nyq-bench/NyqBench.cpp @@ -1327,9 +1327,11 @@ void NyqBench::OnLargeIcons(wxCommandEvent & e) void NyqBench::OnGo(wxCommandEvent & e) { - // No need to delete...EffectManager will do it - mEffect = new NyquistEffect(wxT("Nyquist Effect Workbench")); - const PluginID & ID = EffectManager::Get().RegisterEffect(mEffect); + auto pEffect = + std::make_unique(L"Nyquist Effect Workbench"); + mEffect = pEffect.get(); + const PluginID & ID = + EffectManager::Get().RegisterEffect(std::move(pEffect)); mEffect->SetCommand(mScript->GetValue()); mEffect->RedirectOutput(); diff --git a/src/ModuleManager.cpp b/src/ModuleManager.cpp index a5d8b8804..75cf00645 100755 --- a/src/ModuleManager.cpp +++ b/src/ModuleManager.cpp @@ -530,26 +530,14 @@ ModuleInterface *ModuleManager::CreateProviderInstance(const PluginID & provider return nullptr; } -ComponentInterface *ModuleManager::CreateInstance(const PluginID & providerID, - const PluginPath & path) +std::unique_ptr ModuleManager::CreateInstance( + const PluginID & providerID, const PluginPath & path) { - if (mDynModules.find(providerID) == mDynModules.end()) - { - return NULL; - } - - return mDynModules[providerID]->CreateInstance(path); -} - -void ModuleManager::DeleteInstance(const PluginID & providerID, - ComponentInterface *instance) -{ - if (mDynModules.find(providerID) == mDynModules.end()) - { - return; - } - - mDynModules[providerID]->DeleteInstance(instance); + if (auto iter = mDynModules.find(providerID); + iter == mDynModules.end()) + return nullptr; + else + return iter->second->CreateInstance(path); } bool ModuleManager::IsProviderValid(const PluginID & WXUNUSED(providerID), diff --git a/src/ModuleManager.h b/src/ModuleManager.h index f749deb71..0111eee8d 100644 --- a/src/ModuleManager.h +++ b/src/ModuleManager.h @@ -96,9 +96,10 @@ public: bool RegisterEffectPlugin(const PluginID & provider, const PluginPath & path, TranslatableString &errMsg); - ModuleInterface *CreateProviderInstance(const PluginID & provider, const PluginPath & path); - ComponentInterface *CreateInstance(const PluginID & provider, const PluginPath & path); - void DeleteInstance(const PluginID & provider, ComponentInterface *instance); + ModuleInterface *CreateProviderInstance( + const PluginID & provider, const PluginPath & path); + std::unique_ptr + CreateInstance(const PluginID & provider, const PluginPath & path); bool IsProviderValid(const PluginID & provider, const PluginPath & path); bool IsPluginValid(const PluginID & provider, const PluginPath & path, bool bFast); diff --git a/src/PluginManager.cpp b/src/PluginManager.cpp index f9f0c254b..8e9f76a2a 100644 --- a/src/PluginManager.cpp +++ b/src/PluginManager.cpp @@ -1088,7 +1088,7 @@ PluginDescriptor::PluginDescriptor() mPluginType = PluginTypeNone; mEnabled = false; mValid = false; - mInstance = NULL; + mInstance = nullptr; mEffectType = EffectTypeNone; mEffectInteractive = false; @@ -1100,21 +1100,13 @@ PluginDescriptor::PluginDescriptor() PluginDescriptor::~PluginDescriptor() { - DeleteInstance(); } -void PluginDescriptor::DeleteInstance() -{ - if (mInstance) - { - ModuleManager::Get().DeleteInstance(GetProviderID(), mInstance); - mInstance = nullptr; - } -} +PluginDescriptor &PluginDescriptor::operator =(PluginDescriptor &&) = default; bool PluginDescriptor::IsInstantiated() const { - return mInstance != NULL; + return mInstance != nullptr; } ComponentInterface *PluginDescriptor::GetInstance() @@ -1122,29 +1114,21 @@ ComponentInterface *PluginDescriptor::GetInstance() if (!mInstance) { if (GetPluginType() == PluginTypeModule) - { mInstance = ModuleManager::Get().CreateProviderInstance(GetID(), GetPath()); - } else { - mInstance = ModuleManager::Get().CreateInstance(GetProviderID(), GetPath()); + muInstance = ModuleManager::Get().CreateInstance(GetProviderID(), GetPath()); + mInstance = muInstance.get(); } } return mInstance; } -void PluginDescriptor::SetInstance(ComponentInterface *instance) +void PluginDescriptor::SetInstance(std::unique_ptr instance) { - if (mInstance && mInstance != instance) - { - // Be sure not to leak resources!! - DeleteInstance(); - } - - mInstance = instance; - - return; + muInstance = std::move(instance); + mInstance = muInstance.get(); } PluginType PluginDescriptor::GetPluginType() const @@ -2273,7 +2257,7 @@ void PluginManager::LoadGroup(FileConfig *pRegistry, PluginType type) } // Everything checked out...accept the plugin - mPlugins[groupName] = plug; + mPlugins[groupName] = std::move(plug); } return; @@ -2497,9 +2481,11 @@ bool PluginManager::ShowManager(wxWindow *parent, EffectType type) // Here solely for the purpose of Nyquist Workbench until // a better solution is devised. -const PluginID & PluginManager::RegisterPlugin(EffectDefinitionInterface *effect, PluginType type) +const PluginID & PluginManager::RegisterPlugin( + std::unique_ptr effect, PluginType type) { - PluginDescriptor & plug = CreatePlugin(GetID(effect), effect, type); + PluginDescriptor & plug = + CreatePlugin(GetID(effect.get()), effect.get(), type); plug.SetEffectType(effect->GetType()); plug.SetEffectFamily(effect->GetFamily().Internal()); @@ -2508,7 +2494,7 @@ const PluginID & PluginManager::RegisterPlugin(EffectDefinitionInterface *effect plug.SetEffectRealtime(effect->SupportsRealtime()); plug.SetEffectAutomatable(effect->SupportsAutomation()); - plug.SetInstance(effect); + plug.SetInstance(std::move(effect)); plug.SetEffectLegacy(true); plug.SetEnabled(true); plug.SetValid(true); diff --git a/src/PluginManager.h b/src/PluginManager.h index d7ac23417..817fa3412 100644 --- a/src/PluginManager.h +++ b/src/PluginManager.h @@ -46,11 +46,12 @@ class AUDACITY_DLL_API PluginDescriptor { public: PluginDescriptor(); + PluginDescriptor &operator =(PluginDescriptor &&); virtual ~PluginDescriptor(); bool IsInstantiated() const; ComponentInterface *GetInstance(); - void SetInstance(ComponentInterface *instance); + void SetInstance(std::unique_ptr instance); PluginType GetPluginType() const; void SetPluginType(PluginType type); @@ -121,13 +122,12 @@ public: private: - void DeleteInstance(); - // Common // Among other purposes, PluginDescriptor acts as the resource handle, // or smart pointer, to a resource created in a plugin library, and is responsible // for a cleanup of this pointer. + std::unique_ptr muInstance; // may be null for a module ComponentInterface *mInstance; PluginType mPluginType; @@ -264,7 +264,10 @@ public: bool ShowManager(wxWindow *parent, EffectType type = EffectTypeNone); - const PluginID & RegisterPlugin(EffectDefinitionInterface *effect, PluginType type ); + //! Used only by Nyquist Workbench module + const PluginID & RegisterPlugin( + std::unique_ptr effect, PluginType type ); + //! Used only by Nyquist Workbench module void UnregisterPlugin(const PluginID & ID); private: diff --git a/src/commands/LoadCommands.cpp b/src/commands/LoadCommands.cpp index 1dffca0cf..55fe19078 100644 --- a/src/commands/LoadCommands.cpp +++ b/src/commands/LoadCommands.cpp @@ -194,19 +194,11 @@ bool BuiltinCommandsModule::IsPluginValid(const PluginPath & path, bool bFast) return mCommands.find( path ) != mCommands.end(); } -ComponentInterface *BuiltinCommandsModule::CreateInstance(const PluginPath & path) +std::unique_ptr +BuiltinCommandsModule::CreateInstance(const PluginPath & path) { // Acquires a resource for the application. - // Safety of this depends on complementary calls to DeleteInstance on the module manager side. - return Instantiate(path).release(); -} - -void BuiltinCommandsModule::DeleteInstance(ComponentInterface *instance) -{ - // Releases the resource. - std::unique_ptr < AudacityCommand > { - dynamic_cast(instance) - }; + return Instantiate(path); } // ============================================================================ diff --git a/src/commands/LoadCommands.h b/src/commands/LoadCommands.h index 9aacbdfc9..b7a0153ce 100644 --- a/src/commands/LoadCommands.h +++ b/src/commands/LoadCommands.h @@ -69,8 +69,8 @@ public: bool IsPluginValid(const PluginPath & path, bool bFast) override; - ComponentInterface *CreateInstance(const PluginPath & path) override; - void DeleteInstance(ComponentInterface *instance) override; + std::unique_ptr + CreateInstance(const PluginPath & path) override; private: // BuiltinEffectModule implementation diff --git a/src/effects/EffectManager.cpp b/src/effects/EffectManager.cpp index e983cb6e4..1063429ec 100644 --- a/src/effects/EffectManager.cpp +++ b/src/effects/EffectManager.cpp @@ -56,12 +56,12 @@ EffectManager::~EffectManager() // Here solely for the purpose of Nyquist Workbench until // a better solution is devised. -const PluginID & EffectManager::RegisterEffect(Effect *f) +const PluginID & EffectManager::RegisterEffect(std::unique_ptr uEffect) { - const PluginID & ID = PluginManager::Get().RegisterPlugin(f, PluginTypeEffect); - - mEffects[ID] = f; - + auto pEffect = uEffect.get(); + const PluginID & ID = + PluginManager::Get().RegisterPlugin(std::move(uEffect), PluginTypeEffect); + mEffects[ID] = pEffect; return ID; } diff --git a/src/effects/EffectManager.h b/src/effects/EffectManager.h index eba137d76..5578207f7 100644 --- a/src/effects/EffectManager.h +++ b/src/effects/EffectManager.h @@ -75,10 +75,10 @@ public: EffectManager(); virtual ~EffectManager(); - /** (Un)Register an effect so it can be executed. */ - // Here solely for the purpose of Nyquist Workbench until - // a better solution is devised. - const PluginID & RegisterEffect(Effect *f); + //! Here solely for the purpose of Nyquist Workbench until a better solution is devised. + /** Register an effect so it can be executed. */ + const PluginID & RegisterEffect(std::unique_ptr uEffect); + //! Used only by Nyquist Workbench module void UnregisterEffect(const PluginID & ID); TranslatableString GetEffectFamilyName(const PluginID & ID); diff --git a/src/effects/LoadEffects.cpp b/src/effects/LoadEffects.cpp index ea943fff8..cfe8208a9 100644 --- a/src/effects/LoadEffects.cpp +++ b/src/effects/LoadEffects.cpp @@ -191,19 +191,11 @@ bool BuiltinEffectsModule::IsPluginValid(const PluginPath & path, bool bFast) return mEffects.find( path ) != mEffects.end(); } -ComponentInterface *BuiltinEffectsModule::CreateInstance(const PluginPath & path) +std::unique_ptr +BuiltinEffectsModule::CreateInstance(const PluginPath & path) { // Acquires a resource for the application. - // Safety of this depends on complementary calls to DeleteInstance on the module manager side. - return Instantiate(path).release(); -} - -void BuiltinEffectsModule::DeleteInstance(ComponentInterface *instance) -{ - // Releases the resource. - std::unique_ptr < Effect > { - dynamic_cast(instance) - }; + return Instantiate(path); } // ============================================================================ diff --git a/src/effects/LoadEffects.h b/src/effects/LoadEffects.h index 29fdc613a..f0323f250 100644 --- a/src/effects/LoadEffects.h +++ b/src/effects/LoadEffects.h @@ -69,8 +69,8 @@ public: bool IsPluginValid(const PluginPath & path, bool bFast) override; - ComponentInterface *CreateInstance(const PluginPath & path) override; - void DeleteInstance(ComponentInterface *instance) override; + std::unique_ptr + CreateInstance(const PluginPath & path) override; private: // BuiltinEffectModule implementation diff --git a/src/effects/VST/VSTEffect.cpp b/src/effects/VST/VSTEffect.cpp index 9743d6a30..20a2f30c4 100644 --- a/src/effects/VST/VSTEffect.cpp +++ b/src/effects/VST/VSTEffect.cpp @@ -684,19 +684,12 @@ bool VSTEffectsModule::IsPluginValid(const PluginPath & path, bool bFast) return wxFileName::FileExists(realPath) || wxFileName::DirExists(realPath); } -ComponentInterface *VSTEffectsModule::CreateInstance(const PluginPath & path) +std::unique_ptr +VSTEffectsModule::CreateInstance(const PluginPath & path) { // Acquires a resource for the application. // For us, the ID is simply the path to the effect - // Safety of this depends on complementary calls to DeleteInstance on the module manager side. - return safenew VSTEffect(path); -} - -void VSTEffectsModule::DeleteInstance(ComponentInterface *instance) -{ - std::unique_ptr < VSTEffect > { - dynamic_cast(instance) - }; + return std::make_unique(path); } // ============================================================================ diff --git a/src/effects/VST/VSTEffect.h b/src/effects/VST/VSTEffect.h index 989d25715..6856773c6 100644 --- a/src/effects/VST/VSTEffect.h +++ b/src/effects/VST/VSTEffect.h @@ -432,8 +432,8 @@ public: bool IsPluginValid(const PluginPath & path, bool bFast) override; - ComponentInterface *CreateInstance(const PluginPath & path) override; - void DeleteInstance(ComponentInterface *instance) override; + std::unique_ptr + CreateInstance(const PluginPath & path) override; // VSTEffectModule implementation diff --git a/src/effects/audiounits/AudioUnitEffect.cpp b/src/effects/audiounits/AudioUnitEffect.cpp index 75889f9a4..e3053239c 100644 --- a/src/effects/audiounits/AudioUnitEffect.cpp +++ b/src/effects/audiounits/AudioUnitEffect.cpp @@ -377,25 +377,13 @@ bool AudioUnitEffectsModule::IsPluginValid(const PluginPath & path, bool bFast) return FindAudioUnit(path, name) != NULL; } -ComponentInterface *AudioUnitEffectsModule::CreateInstance(const PluginPath & path) +std::unique_ptr +AudioUnitEffectsModule::CreateInstance(const PluginPath & path) { // Acquires a resource for the application. - wxString name; - AudioComponent component = FindAudioUnit(path, name); - if (component == NULL) - { - return NULL; - } - - // Safety of this depends on complementary calls to DeleteInstance on the module manager side. - return safenew AudioUnitEffect(path, name, component); -} - -void AudioUnitEffectsModule::DeleteInstance(ComponentInterface *instance) -{ - std::unique_ptr < AudioUnitEffect > { - dynamic_cast(instance) - }; + if (wxString name; auto component = FindAudioUnit(path, name)) + return std::make_unique(path, name, component); + return nullptr; } // ============================================================================ diff --git a/src/effects/audiounits/AudioUnitEffect.h b/src/effects/audiounits/AudioUnitEffect.h index 8263710a4..bc31786dc 100644 --- a/src/effects/audiounits/AudioUnitEffect.h +++ b/src/effects/audiounits/AudioUnitEffect.h @@ -261,8 +261,8 @@ public: bool IsPluginValid(const PluginPath & path, bool bFast) override; - ComponentInterface *CreateInstance(const PluginPath & path) override; - void DeleteInstance(ComponentInterface *instance) override; + std::unique_ptr + CreateInstance(const PluginPath & path) override; // AudioUnitEffectModule implementation diff --git a/src/effects/ladspa/LadspaEffect.cpp b/src/effects/ladspa/LadspaEffect.cpp index 3c47ac8d2..363aa1a39 100644 --- a/src/effects/ladspa/LadspaEffect.cpp +++ b/src/effects/ladspa/LadspaEffect.cpp @@ -330,7 +330,8 @@ bool LadspaEffectsModule::IsPluginValid(const PluginPath & path, bool bFast) return wxFileName::FileExists(realPath); } -ComponentInterface *LadspaEffectsModule::CreateInstance(const PluginPath & path) +std::unique_ptr +LadspaEffectsModule::CreateInstance(const PluginPath & path) { // Acquires a resource for the application. // For us, the path is two words. @@ -339,16 +340,7 @@ ComponentInterface *LadspaEffectsModule::CreateInstance(const PluginPath & path) long index; wxString realPath = path.BeforeFirst(wxT(';')); path.AfterFirst(wxT(';')).ToLong(&index); - - // Safety of this depends on complementary calls to DeleteInstance on the module manager side. - return safenew LadspaEffect(realPath, (int)index); -} - -void LadspaEffectsModule::DeleteInstance(ComponentInterface *instance) -{ - std::unique_ptr < LadspaEffect > { - dynamic_cast(instance) - }; + return std::make_unique(realPath, (int)index); } FilePaths LadspaEffectsModule::GetSearchPaths() diff --git a/src/effects/ladspa/LadspaEffect.h b/src/effects/ladspa/LadspaEffect.h index 93619a121..9971f69eb 100644 --- a/src/effects/ladspa/LadspaEffect.h +++ b/src/effects/ladspa/LadspaEffect.h @@ -237,8 +237,8 @@ public: bool IsPluginValid(const PluginPath & path, bool bFast) override; - ComponentInterface *CreateInstance(const PluginPath & path) override; - void DeleteInstance(ComponentInterface *instance) override; + std::unique_ptr + CreateInstance(const PluginPath & path) override; // LadspaEffectModule implementation diff --git a/src/effects/lv2/LoadLV2.cpp b/src/effects/lv2/LoadLV2.cpp index b101ee734..43c82b5d6 100755 --- a/src/effects/lv2/LoadLV2.cpp +++ b/src/effects/lv2/LoadLV2.cpp @@ -302,24 +302,13 @@ bool LV2EffectsModule::IsPluginValid(const PluginPath & path, bool bFast) return GetPlugin(path) != NULL; } -ComponentInterface *LV2EffectsModule::CreateInstance(const PluginPath & path) +std::unique_ptr +LV2EffectsModule::CreateInstance(const PluginPath & path) { // Acquires a resource for the application. - const LilvPlugin *plug = GetPlugin(path); - if (!plug) - { - return NULL; - } - - // Safety of this depends on complementary calls to DeleteInstance on the module manager side. - return safenew LV2Effect(plug); -} - -void LV2EffectsModule::DeleteInstance(ComponentInterface *instance) -{ - std::unique_ptr < LV2Effect > { - dynamic_cast(instance) - }; + if (auto plug = GetPlugin(path)) + return std::make_unique(plug); + return nullptr; } // ============================================================================ diff --git a/src/effects/lv2/LoadLV2.h b/src/effects/lv2/LoadLV2.h index 35430aa26..a214ea0fd 100755 --- a/src/effects/lv2/LoadLV2.h +++ b/src/effects/lv2/LoadLV2.h @@ -192,8 +192,8 @@ public: bool IsPluginValid(const PluginPath & path, bool bFast) override; - ComponentInterface *CreateInstance(const PluginPath & path) override; - void DeleteInstance(ComponentInterface *instance) override; + std::unique_ptr + CreateInstance(const PluginPath & path) override; // LV2EffectModule implementation diff --git a/src/effects/nyquist/LoadNyquist.cpp b/src/effects/nyquist/LoadNyquist.cpp index e1dcb66e0..0f16de43e 100644 --- a/src/effects/nyquist/LoadNyquist.cpp +++ b/src/effects/nyquist/LoadNyquist.cpp @@ -264,24 +264,14 @@ bool NyquistEffectsModule::IsPluginValid(const PluginPath & path, bool bFast) return wxFileName::FileExists(path); } -ComponentInterface *NyquistEffectsModule::CreateInstance(const PluginPath & path) +std::unique_ptr +NyquistEffectsModule::CreateInstance(const PluginPath & path) { // Acquires a resource for the application. auto effect = std::make_unique(path); if (effect->IsOk()) - { - // Safety of this depends on complementary calls to DeleteInstance on the module manager side. - return effect.release(); - } - - return NULL; -} - -void NyquistEffectsModule::DeleteInstance(ComponentInterface *instance) -{ - std::unique_ptr < NyquistEffect > { - dynamic_cast(instance) - }; + return effect; + return nullptr; } // ============================================================================ diff --git a/src/effects/nyquist/LoadNyquist.h b/src/effects/nyquist/LoadNyquist.h index 77016c78a..9220d063e 100644 --- a/src/effects/nyquist/LoadNyquist.h +++ b/src/effects/nyquist/LoadNyquist.h @@ -51,6 +51,6 @@ public: bool IsPluginValid(const PluginPath & path, bool bFast) override; - ComponentInterface *CreateInstance(const PluginPath & path) override; - void DeleteInstance(ComponentInterface *instance) override; + std::unique_ptr + CreateInstance(const PluginPath & path) override; }; diff --git a/src/effects/vamp/LoadVamp.cpp b/src/effects/vamp/LoadVamp.cpp index 589f922bb..4ce2ee666 100644 --- a/src/effects/vamp/LoadVamp.cpp +++ b/src/effects/vamp/LoadVamp.cpp @@ -236,27 +236,16 @@ bool VampEffectsModule::IsPluginValid(const PluginPath & path, bool bFast) return bool(vp); } -ComponentInterface *VampEffectsModule::CreateInstance(const PluginPath & path) +std::unique_ptr +VampEffectsModule::CreateInstance(const PluginPath & path) { // Acquires a resource for the application. int output; bool hasParameters; - auto vp = FindPlugin(path, output, hasParameters); - if (vp) - { - // Safety of this depends on complementary calls to DeleteInstance on the module manager side. - return safenew VampEffect(std::move(vp), path, output, hasParameters); - } - - return NULL; -} - -void VampEffectsModule::DeleteInstance(ComponentInterface *instance) -{ - std::unique_ptr < VampEffect > { - dynamic_cast(instance) - }; + if (auto vp = FindPlugin(path, output, hasParameters)) + return std::make_unique(std::move(vp), path, output, hasParameters); + return nullptr; } // VampEffectsModule implementation diff --git a/src/effects/vamp/LoadVamp.h b/src/effects/vamp/LoadVamp.h index b79667518..285f0f022 100644 --- a/src/effects/vamp/LoadVamp.h +++ b/src/effects/vamp/LoadVamp.h @@ -58,8 +58,8 @@ public: bool IsPluginValid(const PluginPath & path, bool bFast) override; - ComponentInterface *CreateInstance(const PluginPath & path) override; - void DeleteInstance(ComponentInterface *instance) override; + std::unique_ptr + CreateInstance(const PluginPath & path) override; private: // VampEffectModule implementation From 731ab8d55472eb5301ba1e4b1bef5868c5122083 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Sat, 19 Jun 2021 06:10:05 -0400 Subject: [PATCH 02/10] Simplify repeated lookups and iterations in PluginManager --- src/Menus.h | 1 - src/PluginManager.cpp | 159 +++++++++++++++--------------------------- 2 files changed, 58 insertions(+), 102 deletions(-) diff --git a/src/Menus.h b/src/Menus.h index e2ebc4107..d010db98c 100644 --- a/src/Menus.h +++ b/src/Menus.h @@ -22,7 +22,6 @@ class wxCommandEvent; class AudacityProject; class CommandContext; class CommandManager; -class PluginDescriptor; class Track; class TrackList; class ViewInfo; diff --git a/src/PluginManager.cpp b/src/PluginManager.cpp index 8e9f76a2a..42c613bca 100644 --- a/src/PluginManager.cpp +++ b/src/PluginManager.cpp @@ -1422,18 +1422,14 @@ RegistryPath PluginManager::GetPluginEnabledSetting( bool PluginManager::IsPluginRegistered( const PluginPath &path, const TranslatableString *pName) { - for (PluginMap::iterator iter = mPlugins.begin(); iter != mPlugins.end(); ++iter) - { - auto &descriptor = iter->second; - if (descriptor.GetPath() == path) - { + for (auto &pair : mPlugins) { + if (auto &descriptor = pair.second; descriptor.GetPath() == path) { if (pName) descriptor.SetSymbol( { descriptor.GetSymbol().Internal(), *pName }); return true; } } - return false; } @@ -2056,12 +2052,8 @@ void PluginManager::LoadGroup(FileConfig *pRegistry, PluginType type) groupName = ConvertID(groupName); // Bypass group if the ID is already in use - if (mPlugins.find(groupName) != mPlugins.end()) - { - pRegistry->SetPath(wxT("..")); - + if (mPlugins.count(groupName)) continue; - } // Set the ID and type plug.SetID(groupName); @@ -2071,10 +2063,8 @@ void PluginManager::LoadGroup(FileConfig *pRegistry, PluginType type) if (!pRegistry->Read(KEY_PROVIDERID, &strVal, wxEmptyString)) { // Bypass group if the provider isn't valid - if (!strVal.empty() && mPlugins.find(strVal) == mPlugins.end()) - { + if (!strVal.empty() && !mPlugins.count(strVal)) continue; - } } plug.SetProviderID(PluginID(strVal)); @@ -2297,9 +2287,8 @@ void PluginManager::Save() void PluginManager::SaveGroup(FileConfig *pRegistry, PluginType type) { wxString group = GetPluginTypeString(type); - for (PluginMap::iterator iter = mPlugins.begin(); iter != mPlugins.end(); ++iter) - { - PluginDescriptor & plug = iter->second; + for (auto &pair : mPlugins) { + auto & plug = pair.second; if (plug.GetPluginType() != type) { @@ -2383,21 +2372,14 @@ void PluginManager::SaveGroup(FileConfig *pRegistry, PluginType type) // and built-ins. void PluginManager::CheckForUpdates(bool bFast) { - // Get ModuleManager reference ModuleManager & mm = ModuleManager::Get(); - wxArrayString pathIndex; - for (PluginMap::iterator iter = mPlugins.begin(); iter != mPlugins.end(); ++iter) - { - PluginDescriptor & plug = iter->second; + for (auto &pair : mPlugins) { + auto &plug = pair.second; // Bypass 2.1.0 placeholders...remove this after a few releases past 2.1.0 - if (plug.GetPluginType() == PluginTypeNone) - { - continue; - } - - pathIndex.push_back(plug.GetPath().BeforeFirst(wxT(';'))); + if (plug.GetPluginType() != PluginTypeNone) + pathIndex.push_back(plug.GetPath().BeforeFirst(wxT(';'))); } // Check all known plugins to ensure they are still valid and scan for NEW ones. @@ -2411,9 +2393,8 @@ void PluginManager::CheckForUpdates(bool bFast) // // When the user enables the plugin, each provider that reported it will be asked // to register the plugin. - for (PluginMap::iterator iter = mPlugins.begin(); iter != mPlugins.end(); ++iter) - { - PluginDescriptor & plug = iter->second; + for (auto &pair : mPlugins) { + auto &plug = pair.second; const PluginID & plugID = plug.GetID(); const wxString & plugPath = plug.GetPath(); PluginType plugType = plug.GetPluginType(); @@ -2506,37 +2487,21 @@ const PluginID & PluginManager::RegisterPlugin( // a better solution is devised. void PluginManager::UnregisterPlugin(const PluginID & ID) { - if (mPlugins.find(ID) == mPlugins.end()) - { - return; - } - mPlugins.erase(ID); } int PluginManager::GetPluginCount(PluginType type) { - int num = 0; - - for (PluginMap::iterator iter = mPlugins.begin(); iter != mPlugins.end(); ++iter) - { - if (iter->second.GetPluginType() == type) - { - num++; - } - } - - return num; + return count_if(mPlugins.begin(), mPlugins.end(), [type](auto &pair){ + return pair.second.GetPluginType() == type; }); } const PluginDescriptor *PluginManager::GetPlugin(const PluginID & ID) { - if (mPlugins.find(ID) == mPlugins.end()) - { - return NULL; - } - - return &mPlugins[ID]; + if (auto iter = mPlugins.find(ID); iter == mPlugins.end()) + return nullptr; + else + return &iter->second; } const PluginDescriptor *PluginManager::GetFirstPlugin(int type) @@ -2630,56 +2595,49 @@ const PluginDescriptor *PluginManager::GetNextPluginForEffectType(EffectType typ bool PluginManager::IsPluginEnabled(const PluginID & ID) { - if (mPlugins.find(ID) == mPlugins.end()) - { + if (auto iter = mPlugins.find(ID); iter == mPlugins.end()) return false; - } - - return mPlugins[ID].IsEnabled(); + else + return iter->second.IsEnabled(); } void PluginManager::EnablePlugin(const PluginID & ID, bool enable) { - if (mPlugins.find(ID) == mPlugins.end()) - { + if (auto iter = mPlugins.find(ID); iter == mPlugins.end()) return; - } - - return mPlugins[ID].SetEnabled(enable); + else + iter->second.SetEnabled(enable); } const ComponentInterfaceSymbol & PluginManager::GetSymbol(const PluginID & ID) { - if (mPlugins.find(ID) == mPlugins.end()) - { + if (auto iter = mPlugins.find(ID); iter == mPlugins.end()) { static ComponentInterfaceSymbol empty; return empty; } - - return mPlugins[ID].GetSymbol(); + else + return iter->second.GetSymbol(); } ComponentInterface *PluginManager::GetInstance(const PluginID & ID) { - if (mPlugins.find(ID) == mPlugins.end()) - { - return NULL; - } + if (auto iter = mPlugins.find(ID); iter == mPlugins.end()) + return nullptr; + else { + auto &plug = iter->second; - PluginDescriptor & plug = mPlugins[ID]; - - // If not dealing with legacy effects, make sure the provider is loaded - if (!plug.IsEffectLegacy()) - { - const PluginID & prov = plug.GetProviderID(); - if (mPlugins.find(prov) == mPlugins.end()) + // If not dealing with legacy effects, make sure the provider is loaded + if (!plug.IsEffectLegacy()) { - return NULL; + const PluginID & prov = plug.GetProviderID(); + if (auto iter2 = mPlugins.find(prov); iter2 == mPlugins.end()) + return nullptr; + else + iter2->second.GetInstance(); } - mPlugins[prov].GetInstance(); - } - return plug.GetInstance(); + return plug.GetInstance(); + } } PluginID PluginManager::GetID(ModuleInterface *module) @@ -3005,26 +2963,25 @@ RegistryPath PluginManager::SettingsPath(const PluginID & ID, bool shared) // be changed across Audacity versions, or else compatibility of the // configuration files will break. - if (mPlugins.find(ID) == mPlugins.end()) - { - return wxEmptyString; + if (auto iter = mPlugins.find(ID); iter == mPlugins.end()) + return {}; + else { + const PluginDescriptor & plug = iter->second; + + wxString id = GetPluginTypeString(plug.GetPluginType()) + + wxT("_") + + plug.GetEffectFamily() + // is empty for non-Effects + wxT("_") + + plug.GetVendor() + + wxT("_") + + (shared ? wxString{} : plug.GetSymbol().Internal()); + + return SETROOT + + ConvertID(id) + + wxCONFIG_PATH_SEPARATOR + + (shared ? wxT("shared") : wxT("private")) + + wxCONFIG_PATH_SEPARATOR; } - - const PluginDescriptor & plug = mPlugins[ID]; - - wxString id = GetPluginTypeString(plug.GetPluginType()) + - wxT("_") + - plug.GetEffectFamily() + // is empty for non-Effects - wxT("_") + - plug.GetVendor() + - wxT("_") + - (shared ? wxString{} : plug.GetSymbol().Internal()); - - return SETROOT + - ConvertID(id) + - wxCONFIG_PATH_SEPARATOR + - (shared ? wxT("shared") : wxT("private")) + - wxCONFIG_PATH_SEPARATOR; } /* Return value is a key for lookup in a config file */ From 04a0292d1df585b3affe42838e3b3fd375c732b1 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Sat, 19 Jun 2021 08:58:07 -0400 Subject: [PATCH 03/10] Simplify iterations over PluginManager; remove a friend --- src/BatchCommands.cpp | 25 ++--- src/PluginManager.cpp | 158 +++++++++++--------------------- src/PluginManager.h | 57 +++++++++--- src/commands/GetInfoCommand.cpp | 10 +- src/effects/EffectManager.cpp | 15 ++- src/menus/PluginMenus.cpp | 6 +- src/prefs/EffectsPrefs.cpp | 10 +- 7 files changed, 119 insertions(+), 162 deletions(-) diff --git a/src/BatchCommands.cpp b/src/BatchCommands.cpp index 60dc787e1..141f84c75 100644 --- a/src/BatchCommands.cpp +++ b/src/BatchCommands.cpp @@ -308,17 +308,15 @@ MacroCommandsCatalog::MacroCommandsCatalog( const AudacityProject *project ) PluginManager & pm = PluginManager::Get(); EffectManager & em = EffectManager::Get(); { - const PluginDescriptor *plug = pm.GetFirstPlugin(PluginTypeEffect|PluginTypeAudacityCommand); - while (plug) - { - auto command = em.GetCommandIdentifier(plug->GetID()); + for (auto &plug + : pm.PluginsOfType(PluginTypeEffect|PluginTypeAudacityCommand)) { + auto command = em.GetCommandIdentifier(plug.GetID()); if (!command.empty()) commands.push_back( { - { command, plug->GetSymbol().Msgid() }, - plug->GetPluginType() == PluginTypeEffect ? + { command, plug.GetSymbol().Msgid() }, + plug.GetPluginType() == PluginTypeEffect ? XO("Effect") : XO("Menu Command (With Parameters)") } ); - plug = pm.GetNextPlugin(PluginTypeEffect|PluginTypeAudacityCommand); } } @@ -608,19 +606,12 @@ bool MacroCommands::HandleTextualCommand( CommandManager &commandManager, // Not one of the singleton commands. // We could/should try all the list-style commands. // instead we only try the effects. - PluginManager & pm = PluginManager::Get(); EffectManager & em = EffectManager::Get(); - const PluginDescriptor *plug = pm.GetFirstPlugin(PluginTypeEffect); - while (plug) - { - if (em.GetCommandIdentifier(plug->GetID()) == Str) - { + for (auto &plug : PluginManager::Get().PluginsOfType(PluginTypeEffect)) + if (em.GetCommandIdentifier(plug.GetID()) == Str) return EffectUI::DoEffect( - plug->GetID(), context, + plug.GetID(), context, EffectManager::kConfigured); - } - plug = pm.GetNextPlugin(PluginTypeEffect); - } return false; } diff --git a/src/PluginManager.cpp b/src/PluginManager.cpp index 42c613bca..d4600e322 100644 --- a/src/PluginManager.cpp +++ b/src/PluginManager.cpp @@ -627,15 +627,10 @@ void PluginRegistrationDialog::PopulateOrExchange(ShuttleGui &S) } PluginManager & pm = PluginManager::Get(); - for (PluginMap::iterator iter = pm.mPlugins.begin(); iter != pm.mPlugins.end(); ++iter) - { - PluginDescriptor & plug = iter->second; - + for (auto &plug : pm.AllPlugins()) { PluginType plugType = plug.GetPluginType(); if (plugType != PluginTypeEffect && plugType != PluginTypeStub) - { continue; - } const auto &path = plug.GetPath(); ItemData & item = mItems[path]; // will create NEW entry @@ -1025,10 +1020,9 @@ void PluginRegistrationDialog::OnOK(wxCommandEvent & WXUNUSED(evt)) if (mm.RegisterEffectPlugin(item.plugs[j]->GetProviderID(), path, errMsg)) { - for (size_t k = 0, cntk = item.plugs.size(); k < cntk; k++) - { - pm.mPlugins.erase(item.plugs[k]->GetProviderID() + wxT("_") + path); - } + for (auto plug : item.plugs) + pm.UnregisterPlugin( + plug->GetProviderID() + wxT("_") + path); // Bug 1893. We've found a provider that works. // Error messages from any that failed are no longer useful. errMsgs = {}; @@ -1047,19 +1041,14 @@ void PluginRegistrationDialog::OnOK(wxCommandEvent & WXUNUSED(evt)) XO("Effect or Command at %s failed to register:\n%s") .Format( path, errMsgs ) ); } - else if (item.state == STATE_New) - { - for (size_t j = 0, cnt = item.plugs.size(); j < cnt; j++) - { - item.plugs[j]->SetValid(false); - } + else if (item.state == STATE_New) { + for (auto plug : item.plugs) + plug->SetValid(false); } - else if (item.state != STATE_New) - { - for (size_t j = 0, cnt = item.plugs.size(); j < cnt; j++) - { - item.plugs[j]->SetEnabled(item.state == STATE_Enabled); - item.plugs[j]->SetValid(item.valid); + else if (item.state != STATE_New) { + for (auto plug : item.plugs) { + plug->SetEnabled(item.state == STATE_Enabled); + plug->SetValid(item.valid); } } } @@ -1388,7 +1377,7 @@ const PluginID &PluginManagerInterface::AudacityCommandRegistrationCallback( return empty; } -RegistryPath PluginManager::GetPluginEnabledSetting( const PluginID &ID ) +RegistryPath PluginManager::GetPluginEnabledSetting( const PluginID &ID ) const { auto pPlugin = GetPlugin( ID ); if ( pPlugin ) @@ -1397,7 +1386,7 @@ RegistryPath PluginManager::GetPluginEnabledSetting( const PluginID &ID ) } RegistryPath PluginManager::GetPluginEnabledSetting( - const PluginDescriptor &desc ) + const PluginDescriptor &desc ) const { switch ( desc.GetPluginType() ) { case PluginTypeModule: { @@ -1802,12 +1791,9 @@ bool PluginManager::DropFile(const wxString &fileName) auto &mm = ModuleManager::Get(); const wxFileName src{ fileName }; - for (const PluginDescriptor *plug = GetFirstPlugin(PluginTypeModule); - plug; - plug = GetNextPlugin(PluginTypeModule)) - { + for (auto &plug : PluginsOfType(PluginTypeModule)) { auto module = static_cast - (mm.CreateProviderInstance(plug->GetID(), plug->GetPath())); + (mm.CreateProviderInstance(plug.GetID(), plug.GetPath())); if (! module) continue; @@ -2483,8 +2469,6 @@ const PluginID & PluginManager::RegisterPlugin( return plug.GetID(); } -// Here solely for the purpose of Nyquist Workbench until -// a better solution is devised. void PluginManager::UnregisterPlugin(const PluginID & ID) { mPlugins.erase(ID); @@ -2496,7 +2480,7 @@ int PluginManager::GetPluginCount(PluginType type) return pair.second.GetPluginType() == type; }); } -const PluginDescriptor *PluginManager::GetPlugin(const PluginID & ID) +const PluginDescriptor *PluginManager::GetPlugin(const PluginID & ID) const { if (auto iter = mPlugins.find(ID); iter == mPlugins.end()) return nullptr; @@ -2504,93 +2488,57 @@ const PluginDescriptor *PluginManager::GetPlugin(const PluginID & ID) return &iter->second; } -const PluginDescriptor *PluginManager::GetFirstPlugin(int type) +void PluginManager::Iterator::Advance(bool incrementing) { - for (mPluginsIter = mPlugins.begin(); mPluginsIter != mPlugins.end(); ++mPluginsIter) - { - PluginDescriptor & plug = mPluginsIter->second; - PluginType plugType = plug.GetPluginType(); - if( plug.IsValid() && plug.IsEnabled() && ((plugType & type) != 0)) - { - bool familyEnabled = true; - if( (plugType & PluginTypeEffect) != 0) { + const auto end = mPm.mPlugins.end(); + if (incrementing && mIterator != end) + ++mIterator; + bool all = mPluginType == PluginTypeNone && mEffectType == EffectTypeNone; + for (; mIterator != end; ++mIterator) { + auto &plug = mIterator->second; + if (!all && !(plug.IsValid() && plug.IsEnabled())) + continue; + auto plugType = plug.GetPluginType(); + if ((mPluginType == PluginTypeNone || (plugType & mPluginType)) && + (mEffectType == EffectTypeNone || plug.GetEffectType() == mEffectType)) { + if (!all && (plugType & PluginTypeEffect)) { // This preference may be written by EffectsPrefs - auto setting = GetPluginEnabledSetting( plug ); - familyEnabled = setting.empty() - ? true - : gPrefs->Read( setting, true ); + auto setting = mPm.GetPluginEnabledSetting( plug ); + if (!(setting.empty() || gPrefs->Read( setting, true ))) + continue; } - if (familyEnabled) - return &mPluginsIter->second; + // Pause iteration at this match + break; } } - - return NULL; } -const PluginDescriptor *PluginManager::GetNextPlugin(int type) -{ - while (++mPluginsIter != mPlugins.end()) - { - PluginDescriptor & plug = mPluginsIter->second; - PluginType plugType = plug.GetPluginType(); - if( plug.IsValid() && plug.IsEnabled() && ((plugType & type) != 0)) - { - bool familyEnabled = true; - if( (plugType & PluginTypeEffect) != 0) { - // This preference may be written by EffectsPrefs - auto setting = GetPluginEnabledSetting( plug ); - familyEnabled = setting.empty() - ? true - : gPrefs->Read( setting, true ); - } - if (familyEnabled) - return &mPluginsIter->second; - } - } - - return NULL; +PluginManager::Iterator::Iterator(PluginManager &manager) +: mPm{ manager } +, mIterator{ manager.mPlugins.begin() } +{ } -const PluginDescriptor *PluginManager::GetFirstPluginForEffectType(EffectType type) +PluginManager::Iterator::Iterator(PluginManager &manager, int type) +: mPm{ manager } +, mIterator{ manager.mPlugins.begin() } +, mPluginType{ type } { - for (mPluginsIter = mPlugins.begin(); mPluginsIter != mPlugins.end(); ++mPluginsIter) - { - PluginDescriptor & plug = mPluginsIter->second; - - bool familyEnabled; - // This preference may be written by EffectsPrefs - auto setting = GetPluginEnabledSetting( plug ); - familyEnabled = setting.empty() - ? true - : gPrefs->Read( setting, true ); - if (plug.IsValid() && plug.IsEnabled() && plug.GetEffectType() == type && familyEnabled) - { - return &plug; - } - } - - return NULL; + Advance(false); } -const PluginDescriptor *PluginManager::GetNextPluginForEffectType(EffectType type) +PluginManager::Iterator::Iterator(PluginManager &manager, EffectType type) +: mPm{ manager } +, mIterator{ manager.mPlugins.begin() } +, mEffectType{ type } { - while (++mPluginsIter != mPlugins.end()) - { - PluginDescriptor & plug = mPluginsIter->second; - bool familyEnabled; - // This preference may be written by EffectsPrefs - auto setting = GetPluginEnabledSetting( plug ); - familyEnabled = setting.empty() - ? true - : gPrefs->Read( setting, true ); - if (plug.IsValid() && plug.IsEnabled() && plug.GetEffectType() == type && familyEnabled) - { - return &plug; - } - } + Advance(false); +} - return NULL; +auto PluginManager::Iterator::operator ++() -> Iterator & +{ + Advance(true); + return *this; } bool PluginManager::IsPluginEnabled(const PluginID & ID) diff --git a/src/PluginManager.h b/src/PluginManager.h index 817fa3412..81a27e388 100644 --- a/src/PluginManager.h +++ b/src/PluginManager.h @@ -30,8 +30,7 @@ class FileConfig; // /////////////////////////////////////////////////////////////////////////////// -typedef enum -{ +typedef enum : unsigned { PluginTypeNone = 0, // 2.1.0 placeholder entries...not used by 2.1.1 or greater PluginTypeStub =1, // Used for plugins that have not yet been registered PluginTypeEffect =1<<1, @@ -173,8 +172,8 @@ class AUDACITY_DLL_API PluginManager final : public PluginManagerInterface { public: - RegistryPath GetPluginEnabledSetting( const PluginID &ID ); - RegistryPath GetPluginEnabledSetting( const PluginDescriptor &desc ); + RegistryPath GetPluginEnabledSetting( const PluginID &ID ) const; + RegistryPath GetPluginEnabledSetting( const PluginDescriptor &desc ) const; // PluginManagerInterface implementation @@ -246,13 +245,42 @@ public: static wxString GetPluginTypeString(PluginType type); int GetPluginCount(PluginType type); - const PluginDescriptor *GetPlugin(const PluginID & ID); + const PluginDescriptor *GetPlugin(const PluginID & ID) const; - const PluginDescriptor *GetFirstPlugin(int type); // possible or of several PlugInTypes. - const PluginDescriptor *GetNextPlugin( int type); + //! @name iteration over plugins of certain types, supporting range-for syntax + //! @{ + class Iterator { + public: + //! Iterates all, even disabled + explicit Iterator(PluginManager &manager); + //! Iterates only enabled and matching plugins, with family enabled too if an effect + Iterator(PluginManager &manager, + int pluginType //!< bitwise or of values in PluginType + ); + //! Iterates only enabled and matching effects, with family enabled too + Iterator(PluginManager &manager, EffectType type); + bool operator != (int) const { + return mIterator != mPm.mPlugins.end(); + } + Iterator &operator ++ (); + auto &operator *() const { return mIterator->second; } + private: + void Advance(bool incrementing); + const PluginManager &mPm; + PluginMap::iterator mIterator; + EffectType mEffectType{ EffectTypeNone }; + int mPluginType{ PluginTypeNone }; + }; + struct Range { + Iterator first; + Iterator begin() const { return first; } + int end() const { return 0; } + }; - const PluginDescriptor *GetFirstPluginForEffectType(EffectType type); - const PluginDescriptor *GetNextPluginForEffectType(EffectType type); + Range AllPlugins() { return { Iterator{ *this } }; } + Range PluginsOfType(int type) { return { Iterator{ *this, type } }; } + Range EffectsOfType(EffectType type) { return { Iterator{ *this, type } }; } + //! @} bool IsPluginEnabled(const PluginID & ID); void EnablePlugin(const PluginID & ID, bool enable); @@ -267,17 +295,19 @@ public: //! Used only by Nyquist Workbench module const PluginID & RegisterPlugin( std::unique_ptr effect, PluginType type ); - //! Used only by Nyquist Workbench module void UnregisterPlugin(const PluginID & ID); + //! Load from preferences + void Load(); + //! Save to preferences + void Save(); + private: // private! Use Get() PluginManager(); ~PluginManager(); - void Load(); void LoadGroup(FileConfig *pRegistry, PluginType type); - void Save(); void SaveGroup(FileConfig *pRegistry, PluginType type); PluginDescriptor & CreatePlugin(const PluginID & id, ComponentInterface *ident, PluginType type); @@ -324,9 +354,6 @@ private: int mCurrentIndex; PluginMap mPlugins; - PluginMap::iterator mPluginsIter; - - friend class PluginRegistrationDialog; }; // Defining these special names in the low-level PluginManager.h diff --git a/src/commands/GetInfoCommand.cpp b/src/commands/GetInfoCommand.cpp index a99137dd9..289e12042 100644 --- a/src/commands/GetInfoCommand.cpp +++ b/src/commands/GetInfoCommand.cpp @@ -416,14 +416,12 @@ bool GetInfoCommand::SendCommands(const CommandContext &context, int flags ) PluginManager & pm = PluginManager::Get(); EffectManager & em = EffectManager::Get(); { - const PluginDescriptor *plug = pm.GetFirstPlugin(PluginTypeEffect | PluginTypeAudacityCommand); - while (plug) - { - auto command = em.GetCommandIdentifier(plug->GetID()); + for (auto &plug + : pm.PluginsOfType(PluginTypeEffect | PluginTypeAudacityCommand)) { + auto command = em.GetCommandIdentifier(plug.GetID()); if (!command.empty()){ - em.GetCommandDefinition( plug->GetID(), context, flags ); + em.GetCommandDefinition( plug.GetID(), context, flags ); } - plug = pm.GetNextPlugin(PluginTypeEffect | PluginTypeAudacityCommand ); } } context.EndArray(); diff --git a/src/effects/EffectManager.cpp b/src/effects/EffectManager.cpp index 1063429ec..1e2956013 100644 --- a/src/effects/EffectManager.cpp +++ b/src/effects/EffectManager.cpp @@ -826,15 +826,12 @@ const PluginID & EffectManager::GetEffectByIdentifier(const CommandID & strTarge PluginManager & pm = PluginManager::Get(); // Effects OR Generic commands... - const PluginDescriptor *plug = pm.GetFirstPlugin(PluginTypeEffect | PluginTypeAudacityCommand); - while (plug) - { - if (GetCommandIdentifier(plug->GetID()) == strTarget) - { - return plug->GetID(); - } - plug = pm.GetNextPlugin(PluginTypeEffect | PluginTypeAudacityCommand); + for (auto &plug + : pm.PluginsOfType(PluginTypeEffect | PluginTypeAudacityCommand)) { + auto &ID = plug.GetID(); + if (GetCommandIdentifier(ID) == strTarget) + return ID; } - return empty;; + return empty; } diff --git a/src/menus/PluginMenus.cpp b/src/menus/PluginMenus.cpp index 8eaeba3da..aa8821fad 100644 --- a/src/menus/PluginMenus.cpp +++ b/src/menus/PluginMenus.cpp @@ -302,9 +302,8 @@ MenuTable::BaseItemPtrs PopulateEffectsMenu( std::vector optplugs; EffectManager & em = EffectManager::Get(); - const PluginDescriptor *plug = pm.GetFirstPluginForEffectType(type); - while (plug) - { + for (auto &plugin : pm.EffectsOfType(type)) { + auto plug = &plugin; if( plug->IsInstantiated() && em.IsHidden(plug->GetID()) ) continue; if ( !plug->IsEnabled() ){ @@ -322,7 +321,6 @@ MenuTable::BaseItemPtrs PopulateEffectsMenu( defplugs.push_back(plug); else optplugs.push_back(plug); - plug = pm.GetNextPluginForEffectType(type); } wxString groupby = EffectsGroupBy.Read(); diff --git a/src/prefs/EffectsPrefs.cpp b/src/prefs/EffectsPrefs.cpp index d01dd2df2..2bc0f47c6 100644 --- a/src/prefs/EffectsPrefs.cpp +++ b/src/prefs/EffectsPrefs.cpp @@ -137,10 +137,8 @@ static const std::vector< Entry > &GetModuleData() struct ModuleData : public std::vector< Entry > { ModuleData() { auto &pm = PluginManager::Get(); - for (auto plug = pm.GetFirstPlugin(PluginTypeModule); - plug; - plug = pm.GetNextPlugin(PluginTypeModule)) { - auto internal = plug->GetEffectFamily(); + for (auto &plug : pm.PluginsOfType(PluginTypeModule)) { + auto internal = plug.GetEffectFamily(); if ( internal.empty() ) continue; @@ -153,11 +151,11 @@ static const std::vector< Entry > &GetModuleData() // If there should be new modules, it is not important for them // to follow the " Effects" convention, but instead they can // have shorter msgids. - prompt = plug->GetSymbol().Msgid(); + prompt = plug.GetSymbol().Msgid(); else prompt = iter->second; - auto setting = pm.GetPluginEnabledSetting( *plug ); + auto setting = pm.GetPluginEnabledSetting( plug ); push_back( { prompt, setting } ); } From 2ffd9d84c887202b67f02dcffbc677ece5b6cd93 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Sat, 19 Jun 2021 19:37:24 -0400 Subject: [PATCH 04/10] Restrict access to mutating member functions of PluginDescriptor... ... just enough for the PluginRegistrationDialog to do its work --- src/PluginManager.h | 47 ++++++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/src/PluginManager.h b/src/PluginManager.h index 81a27e388..9317253e0 100644 --- a/src/PluginManager.h +++ b/src/PluginManager.h @@ -49,11 +49,8 @@ public: virtual ~PluginDescriptor(); bool IsInstantiated() const; - ComponentInterface *GetInstance(); - void SetInstance(std::unique_ptr instance); PluginType GetPluginType() const; - void SetPluginType(PluginType type); // All plugins @@ -71,17 +68,6 @@ public: bool IsEnabled() const; bool IsValid() const; - // These should be passed an untranslated value - void SetID(const PluginID & ID); - void SetProviderID(const PluginID & providerID); - void SetPath(const PluginPath & path); - void SetSymbol(const ComponentInterfaceSymbol & symbol); - - // These should be passed an untranslated value wrapped in XO() so - // the value will still be extracted for translation - void SetVersion(const wxString & version); - void SetVendor(const wxString & vendor); - void SetEnabled(bool enable); void SetValid(bool valid); @@ -100,6 +86,31 @@ public: bool IsEffectRealtime() const; bool IsEffectAutomatable() const; + // Importer plugins only + + const wxString & GetImporterIdentifier() const; + const TranslatableString & GetImporterFilterDescription() const; + const FileExtensions & GetImporterExtensions() const; + +private: + friend class PluginManager; + + ComponentInterface *GetInstance(); + void SetInstance(std::unique_ptr instance); + + void SetPluginType(PluginType type); + + // These should be passed an untranslated value + void SetID(const PluginID & ID); + void SetProviderID(const PluginID & providerID); + void SetPath(const PluginPath & path); + void SetSymbol(const ComponentInterfaceSymbol & symbol); + + // These should be passed an untranslated value wrapped in XO() so + // the value will still be extracted for translation + void SetVersion(const wxString & version); + void SetVendor(const wxString & vendor); + // "family" should be an untranslated string wrapped in wxT() void SetEffectFamily(const wxString & family); void SetEffectType(EffectType type); @@ -109,18 +120,10 @@ public: void SetEffectRealtime(bool realtime); void SetEffectAutomatable(bool automatable); - // Importer plugins only - - const wxString & GetImporterIdentifier() const; - const TranslatableString & GetImporterFilterDescription() const; - const FileExtensions & GetImporterExtensions() const; - void SetImporterIdentifier(const wxString & identifier); void SetImporterFilterDescription(const TranslatableString & filterDesc); void SetImporterExtensions(FileExtensions extensions); -private: - // Common // Among other purposes, PluginDescriptor acts as the resource handle, From ff0dd1ea48923c39fb4b81dc88fa090da022e2eb Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Tue, 22 Jun 2021 19:51:25 -0400 Subject: [PATCH 05/10] Duplicate PluginManager.cpp in PluginRegistrationDialog.* --- src/PluginRegistrationDialog.cpp | 3139 ++++++++++++++++++++++++++++++ src/PluginRegistrationDialog.h | 3139 ++++++++++++++++++++++++++++++ 2 files changed, 6278 insertions(+) create mode 100644 src/PluginRegistrationDialog.cpp create mode 100644 src/PluginRegistrationDialog.h diff --git a/src/PluginRegistrationDialog.cpp b/src/PluginRegistrationDialog.cpp new file mode 100644 index 000000000..d4600e322 --- /dev/null +++ b/src/PluginRegistrationDialog.cpp @@ -0,0 +1,3139 @@ +/********************************************************************** + + Audacity: A Digital Audio Editor + + PluginManager.cpp + + Leland Lucius + +*******************************************************************//*! + +\file PluginManager.cpp +\brief + +************************************************************************//** +\class PluginManager +\brief PluginManager maintains a list of all plug ins. That covers modules, +effects, generators, analysis-effects, commands. It also has functions +for shared and private configs - which need to move out. +*****************************************************************************/ + + +#include "PluginManager.h" + + + +#include + +#include // for wxUSE_* macros +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "audacity/EffectInterface.h" +#include "audacity/ModuleInterface.h" + +#include "AudacityFileConfig.h" +#include "FileNames.h" +#include "ModuleManager.h" +#include "PlatformCompatibility.h" +#include "Prefs.h" +#include "ShuttleGui.h" +#include "wxFileNameWrapper.h" +#include "widgets/AudacityMessageBox.h" +#include "widgets/ProgressDialog.h" + +#include + +// ============================================================================ +// +// +// +// ============================================================================ +#if wxUSE_ACCESSIBILITY +#include "widgets/WindowAccessible.h" + +class CheckListAx final : public WindowAccessible +{ +public: + CheckListAx(wxListCtrl * window); + + virtual ~ CheckListAx(); + + // Retrieves the address of an IDispatch interface for the specified child. + // All objects must support this property. + wxAccStatus GetChild( int childId, wxAccessible **child ) override; + + // Gets the number of children. + wxAccStatus GetChildCount( int *childCount ) override; + + // Gets the default action for this object (0) or > 0 (the action for a child). + // Return wxACC_OK even if there is no action. actionName is the action, or the empty + // string if there is no action. + // The retrieved string describes the action that is performed on an object, + // not what the object does as a result. For example, a toolbar button that prints + // a document has a default action of "Press" rather than "Prints the current document." + wxAccStatus GetDefaultAction( int childId, wxString *actionName ) override; + + // Returns the description for this object or a child. + wxAccStatus GetDescription( int childId, wxString *description ) override; + + // Gets the window with the keyboard focus. + // If childId is 0 and child is NULL, no object in + // this subhierarchy has the focus. + // If this object has the focus, child should be 'this'. + wxAccStatus GetFocus( int *childId, wxAccessible **child ) override; + + // Returns help text for this object or a child, similar to tooltip text. + wxAccStatus GetHelpText( int childId, wxString *helpText ) override; + + // Returns the keyboard shortcut for this object or child. + // Return e.g. ALT+K + wxAccStatus GetKeyboardShortcut( int childId, wxString *shortcut ) override; + + // Returns the rectangle for this object (id = 0) or a child element (id > 0). + // rect is in screen coordinates. + wxAccStatus GetLocation( wxRect& rect, int elementId ) override; + + // Gets the name of the specified object. + wxAccStatus GetName( int childId, wxString *name ) override; + + // Returns a role constant. + wxAccStatus GetRole( int childId, wxAccRole *role ) override; + + // Gets a variant representing the selected children + // of this object. + // Acceptable values: + // - a null variant (IsNull() returns TRUE) + // - a list variant (GetType() == wxT("list")) + // - an integer representing the selected child element, + // or 0 if this object is selected (GetType() == wxT("long")) + // - a "void*" pointer to a wxAccessible child object + wxAccStatus GetSelections( wxVariant *selections ) override; + + // Returns a state constant. + wxAccStatus GetState( int childId, long* state ) override; + + // Returns a localized string representing the value for the object + // or child. + wxAccStatus GetValue( int childId, wxString *strValue ) override; + + void SetSelected( int item, bool focused = true ); + +private: + wxListCtrl *mParent; + int mLastId; +}; + +CheckListAx::CheckListAx( wxListCtrl * window ) +: WindowAccessible( window ) +{ + mParent = window; + mLastId = -1; +} + +CheckListAx::~CheckListAx() +{ +} + +void CheckListAx::SetSelected( int item, bool focused ) +{ + if (mLastId != -1) + { + NotifyEvent( wxACC_EVENT_OBJECT_SELECTIONREMOVE, + mParent, + wxOBJID_CLIENT, + mLastId ); + mLastId = -1; + } + + if (item != -1) + { + if (focused) + { + NotifyEvent( wxACC_EVENT_OBJECT_FOCUS, + mParent, + wxOBJID_CLIENT, + item + 1 ); + } + + NotifyEvent( wxACC_EVENT_OBJECT_SELECTION, + mParent, + wxOBJID_CLIENT, + item + 1 ); + + mLastId = item + 1; + } +} + +// Retrieves the address of an IDispatch interface for the specified child. +// All objects must support this property. +wxAccStatus CheckListAx::GetChild( int childId, wxAccessible** child ) +{ + if( childId == wxACC_SELF ) + { + *child = this; + } + else + { + *child = NULL; + } + + return wxACC_OK; +} + +// Gets the number of children. +wxAccStatus CheckListAx::GetChildCount( int *childCount ) +{ + *childCount = mParent->GetItemCount(); + + return wxACC_OK; +} + +// Gets the default action for this object (0) or > 0 (the action for a child). +// Return wxACC_OK even if there is no action. actionName is the action, or the empty +// string if there is no action. +// The retrieved string describes the action that is performed on an object, +// not what the object does as a result. For example, a toolbar button that prints +// a document has a default action of "Press" rather than "Prints the current document." +wxAccStatus CheckListAx::GetDefaultAction( int WXUNUSED(childId), wxString *actionName ) +{ + actionName->clear(); + + return wxACC_OK; +} + +// Returns the description for this object or a child. +wxAccStatus CheckListAx::GetDescription( int WXUNUSED(childId), wxString *description ) +{ + description->clear(); + + return wxACC_OK; +} + +// Gets the window with the keyboard focus. +// If childId is 0 and child is NULL, no object in +// this subhierarchy has the focus. +// If this object has the focus, child should be 'this'. +wxAccStatus CheckListAx::GetFocus( int *childId, wxAccessible **child ) +{ + *childId = 0; + *child = this; + + return wxACC_OK; +} + +// Returns help text for this object or a child, similar to tooltip text. +wxAccStatus CheckListAx::GetHelpText( int WXUNUSED(childId), wxString *helpText ) +{ + helpText->clear(); + + return wxACC_OK; +} + +// Returns the keyboard shortcut for this object or child. +// Return e.g. ALT+K +wxAccStatus CheckListAx::GetKeyboardShortcut( int WXUNUSED(childId), wxString *shortcut ) +{ + shortcut->clear(); + + return wxACC_OK; +} + +// Returns the rectangle for this object (id = 0) or a child element (id > 0). +// rect is in screen coordinates. +wxAccStatus CheckListAx::GetLocation( wxRect& rect, int elementId ) +{ + if( elementId == wxACC_SELF ) + { + rect = mParent->GetRect(); + rect.SetPosition( mParent->GetParent()->ClientToScreen( rect.GetPosition() ) ); + } + else + { + if( elementId <= mParent->GetItemCount() ) + { + mParent->GetItemRect( elementId - 1, rect, wxLIST_RECT_LABEL ); + rect.SetPosition( mParent->ClientToScreen( rect.GetPosition() ) ); + } + } + + return wxACC_OK; +} + +// Gets the name of the specified object. +wxAccStatus CheckListAx::GetName( int WXUNUSED(childId), wxString *name ) +{ + *name = mParent->GetName(); + + return wxACC_OK; +} + +// Returns a role constant. +wxAccStatus CheckListAx::GetRole( int childId, wxAccRole *role ) +{ + if( childId == wxACC_SELF ) + { + *role = wxROLE_SYSTEM_LIST; + } + else + { + *role = wxROLE_SYSTEM_LISTITEM; + } + + return wxACC_OK; +} + +// Gets a variant representing the selected children +// of this object. +// Acceptable values: +// - a null variant (IsNull() returns TRUE) +// - a list variant (GetType() == wxT("list")) +// - an integer representing the selected child element, +// or 0 if this object is selected (GetType() == wxT("long")) +// - a "void*" pointer to a wxAccessible child object +wxAccStatus CheckListAx::GetSelections( wxVariant * WXUNUSED(selections) ) +{ + return wxACC_NOT_IMPLEMENTED; +} + +// Returns a state constant. +wxAccStatus CheckListAx::GetState( int childId, long *pState ) +{ + int flag = wxACC_STATE_SYSTEM_FOCUSABLE; + + if( childId == wxACC_SELF ) + { + flag |= wxACC_STATE_SYSTEM_FOCUSED; + } + else + { + wxListItem item; + + item.SetId( childId - 1 ); + item.SetState( wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED ); + item.SetMask( wxLIST_MASK_STATE ); + + if( mParent->GetItem( item ) ) + { + flag |= wxACC_STATE_SYSTEM_SELECTABLE; + + long state = item.GetState(); + + if( state & wxLIST_STATE_FOCUSED ) + { + flag |= wxACC_STATE_SYSTEM_FOCUSED; + } + + if( state & wxLIST_STATE_SELECTED ) + { + flag |= wxACC_STATE_SYSTEM_SELECTED; + } + } + } + + *pState = flag; + + return wxACC_OK; +} + +// Returns a localized string representing the value for the object +// or child. +wxAccStatus CheckListAx::GetValue( int childId, wxString *strValue ) +{ + if( childId == 0 ) + { + return wxACC_OK; + } + else + { + *strValue = mParent->GetItemText( childId - 1 ); + } + + return wxACC_OK; +} + +#endif + +// ============================================================================ +// +// +// +// ============================================================================ + +enum +{ + STATE_Enabled, + STATE_Disabled, + STATE_New, + + STATE_COUNT +}; + +struct ItemData +{ + std::vector plugs; + wxString name; + PluginPath path; + int state; + bool valid; + int nameWidth; + int pathWidth; + int stateWidth; +}; + +using ItemDataMap = std::unordered_map; + +enum +{ + ID_ShowAll = 10000, + ID_ShowEnabled, + ID_ShowDisabled, + ID_ShowNew, + ID_List, + ID_ClearAll, + ID_SelectAll, + ID_Enable, + ID_Disable, +}; + +enum +{ + COL_Name, + COL_State, + COL_Path, + + COL_COUNT +}; + +class PluginRegistrationDialog final : public wxDialogWrapper +{ +public: + // constructors and destructors + PluginRegistrationDialog(wxWindow *parent, EffectType type); + +private: + void Populate(); + void PopulateOrExchange(ShuttleGui & S); + void RegenerateEffectsList(int iShowWhat); + void SetState(int i, bool toggle, bool state = true); + + static int wxCALLBACK SortCompare(wxIntPtr item1, wxIntPtr item2, wxIntPtr sortData); + int SortCompare(ItemData *item1, ItemData *item2); + + void OnChangedVisibility(wxCommandEvent & evt); + void OnSort(wxListEvent & evt); + void DoSort( int col ); + void OnListChar(wxKeyEvent & evt); + void OnOK(wxCommandEvent & evt); + void OnCancel(wxCommandEvent & evt); + void OnSelectAll(wxCommandEvent & evt); + void OnClearAll(wxCommandEvent & evt); + void OnEnable(wxCommandEvent & evt); + void OnDisable(wxCommandEvent & evt); + +private: + EffectType mType; + int mFilter; + + wxArrayString mStates; + ItemDataMap mItems; + + int mSortColumn; + int mSortDirection; + + PluginPath mLongestPath; + + wxListCtrl *mEffects; +#if wxUSE_ACCESSIBILITY + CheckListAx *mAx; +#endif + + DECLARE_EVENT_TABLE() +}; + +BEGIN_EVENT_TABLE(PluginRegistrationDialog, wxDialogWrapper) + EVT_LIST_COL_CLICK(ID_List, PluginRegistrationDialog::OnSort) + EVT_BUTTON(wxID_OK, PluginRegistrationDialog::OnOK) + EVT_BUTTON(wxID_CANCEL, PluginRegistrationDialog::OnCancel) + EVT_BUTTON(ID_ClearAll, PluginRegistrationDialog::OnClearAll) + EVT_BUTTON(ID_SelectAll, PluginRegistrationDialog::OnSelectAll) + EVT_BUTTON(ID_Enable, PluginRegistrationDialog::OnEnable) + EVT_BUTTON(ID_Disable, PluginRegistrationDialog::OnDisable) + EVT_RADIOBUTTON(ID_ShowAll, PluginRegistrationDialog::OnChangedVisibility) + EVT_RADIOBUTTON(ID_ShowEnabled, PluginRegistrationDialog::OnChangedVisibility) + EVT_RADIOBUTTON(ID_ShowDisabled, PluginRegistrationDialog::OnChangedVisibility) + EVT_RADIOBUTTON(ID_ShowNew, PluginRegistrationDialog::OnChangedVisibility) +END_EVENT_TABLE() + +PluginRegistrationDialog::PluginRegistrationDialog(wxWindow *parent, EffectType type) +: wxDialogWrapper(parent, + wxID_ANY, + XO("Manage Plug-ins"), + wxDefaultPosition, wxDefaultSize, + wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) +{ + mType = type; + mEffects = NULL; + SetName(); + + mStates.resize(STATE_COUNT); + mStates[STATE_Enabled] = _("Enabled"); + mStates[STATE_Disabled] = _("Disabled"); + mStates[STATE_New] = _("New"); + + mSortColumn = COL_Name; + mSortDirection = 1; + + Populate(); + + DoSort( mSortColumn ); +} + +void PluginRegistrationDialog::Populate() +{ + //------------------------- Main section -------------------- + ShuttleGui S(this, eIsCreating); + PopulateOrExchange(S); + // ----------------------- End of main section -------------- +} + +/// Defines the dialog and does data exchange with it. +void PluginRegistrationDialog::PopulateOrExchange(ShuttleGui &S) +{ + S.StartVerticalLay(true); + { + /*i18n-hint: The dialog shows a list of plugins with check-boxes + beside each one.*/ +// S.StartStatic(XO("Effects"), true); + S.StartVerticalLay(); + { + S.StartHorizontalLay(wxEXPAND, 0); + { + S.StartHorizontalLay(wxALIGN_LEFT, 0); + { + S.AddPrompt(XXO("Select effects, click the Enable or Disable button, then click OK.")); + } + S.EndHorizontalLay(); + + S.StartHorizontalLay(wxCENTER, 1); + { + S.AddSpace(1); + } + S.EndHorizontalLay(); + + S.StartHorizontalLay(wxALIGN_NOT | wxALIGN_LEFT, 0); + { + wxRadioButton *rb; + + /* i18n-hint: This is before radio buttons selecting which effects to show */ + S.AddPrompt(XXO("Show:")); + rb = S.Id(ID_ShowAll) + /* i18n-hint: Radio button to show all effects */ + .Name(XO("Show all")) + /* i18n-hint: Radio button to show all effects */ + .AddRadioButton(XXO("&All")); +#if wxUSE_ACCESSIBILITY + // so that name can be set on a standard control + rb->SetAccessible(safenew WindowAccessible(rb)); +#endif + + rb = S.Id(ID_ShowDisabled) + /* i18n-hint: Radio button to show just the currently disabled effects */ + .Name(XO("Show disabled")) + /* i18n-hint: Radio button to show just the currently disabled effects */ + .AddRadioButtonToGroup(XXO("D&isabled")); +#if wxUSE_ACCESSIBILITY + // so that name can be set on a standard control + rb->SetAccessible(safenew WindowAccessible(rb)); +#endif + + rb = S.Id(ID_ShowEnabled) + /* i18n-hint: Radio button to show just the currently enabled effects */ + .Name(XO("Show enabled")) + /* i18n-hint: Radio button to show just the currently enabled effects */ + .AddRadioButtonToGroup(XXO("E&nabled")); +#if wxUSE_ACCESSIBILITY + // so that name can be set on a standard control + rb->SetAccessible(safenew WindowAccessible(rb)); +#endif + + rb = S.Id(ID_ShowNew) + /* i18n-hint: Radio button to show just the newly discovered effects */ + .Name(XO("Show new")) + /* i18n-hint: Radio button to show just the newly discovered effects */ + .AddRadioButtonToGroup(XXO("Ne&w")); +#if wxUSE_ACCESSIBILITY + // so that name can be set on a standard control + rb->SetAccessible(safenew WindowAccessible(rb)); +#endif + } + S.EndHorizontalLay(); + } + S.EndHorizontalLay(); + + mEffects = S.Id(ID_List) + .Style(wxSUNKEN_BORDER | wxLC_REPORT | wxLC_HRULES | wxLC_VRULES ) + .ConnectRoot(wxEVT_KEY_DOWN, + &PluginRegistrationDialog::OnListChar) + .AddListControlReportMode({ XO("Name"), XO("State"), XO("Path") }); +#if wxUSE_ACCESSIBILITY + mEffects->SetAccessible(mAx = safenew CheckListAx(mEffects)); +#endif + + S.StartHorizontalLay(wxALIGN_LEFT | wxEXPAND, 0); + { + S.Id(ID_SelectAll).AddButton(XXO("&Select All")); + S.Id(ID_ClearAll).AddButton(XXO("C&lear All")); + + S.StartHorizontalLay(wxALIGN_CENTER); + { + S.AddSpace(1); + } + S.EndHorizontalLay(); + + S.Id(ID_Enable).AddButton(XXO("&Enable")); + S.Id(ID_Disable).AddButton(XXO("&Disable")); + } + S.EndHorizontalLay(); + } +// S.EndStatic(); + S.EndVerticalLay(); + + S.AddStandardButtons(eOkButton | eCancelButton); + } + S.EndVerticalLay(); + + std::vector colWidths; + for (int i = 0, cnt = mEffects->GetColumnCount(); i < cnt; i++) + { + colWidths.push_back(0); + } + + for (int i = 0, cnt = mStates.size(); i < cnt; i++) + { + int x; + mEffects->GetTextExtent(mStates[i], &x, NULL); + colWidths[COL_State] = wxMax(colWidths[COL_State], x + 4); // 2 pixel margin on each side + } + + PluginManager & pm = PluginManager::Get(); + for (auto &plug : pm.AllPlugins()) { + PluginType plugType = plug.GetPluginType(); + if (plugType != PluginTypeEffect && plugType != PluginTypeStub) + continue; + + const auto &path = plug.GetPath(); + ItemData & item = mItems[path]; // will create NEW entry + item.plugs.push_back(&plug); + item.path = path; + item.state = plug.IsEnabled() ? STATE_Enabled : STATE_Disabled; + item.valid = plug.IsValid(); + + if (plugType == PluginTypeEffect) + { + item.name = plug.GetSymbol().Translation(); + } + // This is not right and will not work when other plugin types are added. + // But it's presumed that the plugin manager dialog will be fully developed + // by then. + else if (plugType == PluginTypeStub) + { + wxFileName fname { path }; + item.name = fname.GetName().Trim(false).Trim(true); + if (!item.valid) + { + item.state = STATE_New; + } + } + + int x; + mEffects->GetTextExtent(item.name, &x, NULL); + colWidths[COL_Name] = wxMax(colWidths[COL_Name], x); + + mEffects->GetTextExtent(item.path, &x, NULL); + if (x > colWidths[COL_Path]) + { + mLongestPath = item.path; + } + colWidths[COL_Path] = wxMax(colWidths[COL_Path], x); + } + + wxRect r = wxGetClientDisplayRect(); + + int maxW = 0; + for (int i = 0, cnt = mEffects->GetColumnCount(); i < cnt; i++) + { + int w = colWidths[i] + /* fudge */ 10; + mEffects->SetColumnWidth(i, w); + maxW += w; + } + + // Keep dialog from getting too wide + int w = r.GetWidth() - (GetClientSize().GetWidth() - mEffects->GetSize().GetWidth()); + mEffects->SetMinSize({ std::min(maxW, w), 200 }); + mEffects->SetMaxSize({ w, -1 }); + + RegenerateEffectsList(ID_ShowAll); + + Layout(); + Fit(); + + wxSize sz = GetSize(); + sz.SetWidth(wxMin(sz.GetWidth(), r.GetWidth())); + sz.SetHeight(wxMin(sz.GetHeight(), r.GetHeight())); + SetMinSize(sz); + + // Parent window is usually not there yet, so centre on screen rather than on parent. + CenterOnScreen(); + + if (mEffects->GetItemCount() > 0) + { + // Make sure first item is selected/focused. + mEffects->SetFocus(); + mEffects->SetItemState(0, wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED, wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED); +#if wxUSE_ACCESSIBILITY + mAx->SetSelected(0); +#endif + } + +} + +void PluginRegistrationDialog::RegenerateEffectsList(int filter) +{ + mFilter = filter; + + mEffects->DeleteAllItems(); + + int i = 0; + for (ItemDataMap::iterator iter = mItems.begin(); iter != mItems.end(); ++iter) + { + ItemData & item = iter->second; + bool add = false; + + switch (mFilter) + { + case ID_ShowAll: + add = true; + break; + case ID_ShowNew: + if (item.state == STATE_New) + { + add = true; + } + break; + case ID_ShowEnabled: + if (item.state == STATE_Enabled) + { + add = true; + } + break; + case ID_ShowDisabled: + if (item.state == STATE_Disabled) + { + add = true; + } + break; + } + + if (add) + { + mEffects->InsertItem(i, item.name); + mEffects->SetItem(i, COL_State, mStates[item.state]); + mEffects->SetItem(i, COL_Path, item.path); + mEffects->SetItemPtrData(i, (wxUIntPtr) &item); + + ++i; + } + } + + mEffects->SortItems(SortCompare, (wxUIntPtr) this); + + if (mEffects->GetItemCount() > 0) + { + // Make sure first item is selected/focused. +// mEffects->SetFocus(); + mEffects->SetItemState(0, wxLIST_STATE_FOCUSED|wxLIST_STATE_SELECTED, wxLIST_STATE_FOCUSED|wxLIST_STATE_SELECTED); +#if wxUSE_ACCESSIBILITY + mAx->SetSelected(0, false); +#endif + } +} + +void PluginRegistrationDialog::SetState(int i, bool toggle, bool state) +{ + wxListItem li; + + li.m_mask = wxLIST_MASK_DATA; + li.m_itemId = i; + + mEffects->GetItem(li); + + ItemData *item = (ItemData *) li.m_data; + + // If changing the state of a "New" (stub) entry, then we mark it as valid + // since it will either be registered if "Enabled" or ignored if "Disabled". + if (item->state == STATE_New) + { + item->valid = true; + } + + if (toggle) + { + item->state = item->state == STATE_Enabled ? STATE_Disabled : STATE_Enabled; + } + else + { + item->state = state; + } + + if (mFilter == ID_ShowNew && item->state != STATE_New) + { + mEffects->DeleteItem(i); + } + else if (mFilter == ID_ShowDisabled && item->state != STATE_Disabled) + { + mEffects->DeleteItem(i); + } + else if (mFilter == ID_ShowEnabled && item->state != STATE_Enabled) + { + mEffects->DeleteItem(i); + } + else + { + mEffects->SetItem(i, COL_State, mStates[item->state]); +#if wxUSE_ACCESSIBILITY + mAx->SetSelected(i); +#endif + } +} + +int wxCALLBACK PluginRegistrationDialog::SortCompare(wxIntPtr item1, wxIntPtr item2, wxIntPtr sortData) +{ + PluginRegistrationDialog *dlg = (PluginRegistrationDialog *) sortData; + ItemData *i1 = (ItemData *) item1; + ItemData *i2 = (ItemData *) item2; + + return dlg->SortCompare(i1, i2); +} + +int PluginRegistrationDialog::SortCompare(ItemData *item1, ItemData *item2) +{ + // This function is a three-valued comparator + + wxString *str1; + wxString *str2; + + switch (mSortColumn) + { + case COL_Name: + str1 = &item1->name; + str2 = &item2->name; + break; + case COL_State: + str1 = &mStates[item1->state]; + str2 = &mStates[item2->state]; + break; + case COL_Path: + str1 = &item1->path; + str2 = &item2->path; + break; + default: + return 0; + } + + return str2->CmpNoCase(*str1) * mSortDirection; +} + +void PluginRegistrationDialog::OnChangedVisibility(wxCommandEvent & evt) +{ + // Go and show the relevant items. + RegenerateEffectsList(evt.GetId()); +} + +void PluginRegistrationDialog::OnSort(wxListEvent & evt) +{ + int col = evt.GetColumn(); + DoSort( col ); +} + +void PluginRegistrationDialog::DoSort( int col ) +{ + if (col != mSortColumn) + { + mSortDirection = 1; + } + else + { + mSortDirection *= -1; + } + + mSortColumn = col; + mEffects->SortItems(SortCompare, (wxUIntPtr) this); + + // Without a refresh, wxMac doesn't redisplay the list properly after a sort + mEffects->Refresh(); +} + +void PluginRegistrationDialog::OnListChar(wxKeyEvent & evt) +{ + switch (evt.GetKeyCode()) + { + case WXK_SPACE: + { + int item = mEffects->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_FOCUSED); + if (item != wxNOT_FOUND) + { + SetState(item, true); + } + } + break; + + case WXK_RETURN: + // Don't know why wxListCtrls prevent default dialog action, + // but they do, so handle it. + EmulateButtonClickIfPresent(GetAffirmativeId()); + break; + + default: + evt.Skip(); + break; + } +} + +void PluginRegistrationDialog::OnSelectAll(wxCommandEvent & WXUNUSED(evt)) +{ + for (int i = 0, cnt = mEffects->GetItemCount(); i < cnt; i++) + { + mEffects->SetItemState(i, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); + } +} + +void PluginRegistrationDialog::OnClearAll(wxCommandEvent & WXUNUSED(evt)) +{ + for (int i = 0, cnt = mEffects->GetItemCount(); i < cnt; i++) + { + mEffects->SetItemState(i, 0, wxLIST_STATE_SELECTED); + } +} + +void PluginRegistrationDialog::OnEnable(wxCommandEvent & WXUNUSED(evt)) +{ + std::vector items; + + { + long i = mEffects->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + while (i != wxNOT_FOUND) + { + items.insert(items.begin(), i); + i = mEffects->GetNextItem(i, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + } + } + + for (size_t i = 0, cnt = items.size(); i < cnt; i++) + { + SetState(items[i], false, STATE_Enabled); + } +} + +void PluginRegistrationDialog::OnDisable(wxCommandEvent & WXUNUSED(evt)) +{ + std::vector items; + + { + long i = mEffects->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + while (i != wxNOT_FOUND) + { + items.insert(items.begin(), i); + i = mEffects->GetNextItem(i, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + } + } + + for (size_t i = 0, cnt = items.size(); i < cnt; i++) + { + SetState(items[i], false, STATE_Disabled); + } +} + +void PluginRegistrationDialog::OnOK(wxCommandEvent & WXUNUSED(evt)) +{ + PluginManager & pm = PluginManager::Get(); + ModuleManager & mm = ModuleManager::Get(); + + int enableCount = 0; + for (ItemDataMap::iterator iter = mItems.begin(); iter != mItems.end(); ++iter) + { + ItemData & item = iter->second; + wxString path = item.path; + + if (item.state == STATE_Enabled && item.plugs[0]->GetPluginType() == PluginTypeStub) + { + enableCount++; + } + } + + wxString last3 = mLongestPath + wxT("\n") + + mLongestPath + wxT("\n") + + mLongestPath + wxT("\n"); + + auto msg = XO("Enabling effects or commands:\n\n%s").Format( last3 ); + + // Make sure the progress dialog is deleted before we call EndModal() or + // we will leave the project window in an unusable state on OSX. + // See bug #1192. + { + ProgressDialog progress{ + Verbatim( GetTitle() ), msg, pdlgHideStopButton }; + progress.CenterOnParent(); + + int i = 0; + for (ItemDataMap::iterator iter = mItems.begin(); iter != mItems.end(); ++iter) + { + ItemData & item = iter->second; + wxString path = item.path; + + if (item.state == STATE_Enabled && item.plugs[0]->GetPluginType() == PluginTypeStub) + { + last3 = last3.AfterFirst(wxT('\n')) + item.path + wxT("\n"); + auto status = progress.Update(++i, enableCount, + XO("Enabling effect or command:\n\n%s").Format( last3 )); + if (status == ProgressResult::Cancelled) + { + break; + } + + TranslatableString errMsgs; + + // Try to register the plugin via each provider until one succeeds + for (size_t j = 0, cntj = item.plugs.size(); j < cntj; j++) + { + TranslatableString errMsg; + if (mm.RegisterEffectPlugin(item.plugs[j]->GetProviderID(), path, + errMsg)) + { + for (auto plug : item.plugs) + pm.UnregisterPlugin( + plug->GetProviderID() + wxT("_") + path); + // Bug 1893. We've found a provider that works. + // Error messages from any that failed are no longer useful. + errMsgs = {}; + break; + } + else + { + if (!errMsgs.empty()) + errMsgs.Join( errMsg, '\n' ); + else + errMsgs = errMsg; + } + } + if (!errMsgs.empty()) + AudacityMessageBox( + XO("Effect or Command at %s failed to register:\n%s") + .Format( path, errMsgs ) ); + } + else if (item.state == STATE_New) { + for (auto plug : item.plugs) + plug->SetValid(false); + } + else if (item.state != STATE_New) { + for (auto plug : item.plugs) { + plug->SetEnabled(item.state == STATE_Enabled); + plug->SetValid(item.valid); + } + } + } + + pm.Save(); + } + + EndModal(wxID_OK); +} + +void PluginRegistrationDialog::OnCancel(wxCommandEvent & WXUNUSED(evt)) +{ + EndModal(wxID_CANCEL); +} + + + +/////////////////////////////////////////////////////////////////////////////// +// +// Plugindescriptor +// +/////////////////////////////////////////////////////////////////////////////// + +PluginDescriptor::PluginDescriptor() +{ + mPluginType = PluginTypeNone; + mEnabled = false; + mValid = false; + mInstance = nullptr; + + mEffectType = EffectTypeNone; + mEffectInteractive = false; + mEffectDefault = false; + mEffectLegacy = false; + mEffectRealtime = false; + mEffectAutomatable = false; +} + +PluginDescriptor::~PluginDescriptor() +{ +} + +PluginDescriptor &PluginDescriptor::operator =(PluginDescriptor &&) = default; + +bool PluginDescriptor::IsInstantiated() const +{ + return mInstance != nullptr; +} + +ComponentInterface *PluginDescriptor::GetInstance() +{ + if (!mInstance) + { + if (GetPluginType() == PluginTypeModule) + mInstance = ModuleManager::Get().CreateProviderInstance(GetID(), GetPath()); + else + { + muInstance = ModuleManager::Get().CreateInstance(GetProviderID(), GetPath()); + mInstance = muInstance.get(); + } + } + + return mInstance; +} + +void PluginDescriptor::SetInstance(std::unique_ptr instance) +{ + muInstance = std::move(instance); + mInstance = muInstance.get(); +} + +PluginType PluginDescriptor::GetPluginType() const +{ + return mPluginType; +} + +const PluginID & PluginDescriptor::GetID() const +{ + return mID; +} + +const PluginID & PluginDescriptor::GetProviderID() const +{ + return mProviderID; +} + +const PluginPath & PluginDescriptor::GetPath() const +{ + return mPath; +} + +const ComponentInterfaceSymbol & PluginDescriptor::GetSymbol() const +{ + return mSymbol; +} + +wxString PluginDescriptor::GetUntranslatedVersion() const +{ + return mVersion; +} + +wxString PluginDescriptor::GetVendor() const +{ + return mVendor; +} + +bool PluginDescriptor::IsEnabled() const +{ + return mEnabled; +} + +bool PluginDescriptor::IsValid() const +{ + return mValid; +} + +void PluginDescriptor::SetPluginType(PluginType type) +{ + mPluginType = type; +} + +void PluginDescriptor::SetID(const PluginID & ID) +{ + mID = ID; +} + +void PluginDescriptor::SetProviderID(const PluginID & providerID) +{ + mProviderID = providerID; +} + +void PluginDescriptor::SetPath(const PluginPath & path) +{ + mPath = path; +} + +void PluginDescriptor::SetSymbol(const ComponentInterfaceSymbol & symbol) +{ + mSymbol = symbol; +} + +void PluginDescriptor::SetVersion(const wxString & version) +{ + mVersion = version; +} + +void PluginDescriptor::SetVendor(const wxString & vendor) +{ + mVendor = vendor; +} + +void PluginDescriptor::SetEnabled(bool enable) +{ + mEnabled = enable; +} + +void PluginDescriptor::SetValid(bool valid) +{ + mValid = valid; +} + +// Effects + +wxString PluginDescriptor::GetEffectFamily() const +{ + return mEffectFamily; +} + +EffectType PluginDescriptor::GetEffectType() const +{ + return mEffectType; +} + +bool PluginDescriptor::IsEffectInteractive() const +{ + return mEffectInteractive; +} + +bool PluginDescriptor::IsEffectDefault() const +{ + return mEffectDefault; +} + +bool PluginDescriptor::IsEffectLegacy() const +{ + return mEffectLegacy; +} + +bool PluginDescriptor::IsEffectRealtime() const +{ + return mEffectRealtime; +} + +bool PluginDescriptor::IsEffectAutomatable() const +{ + return mEffectAutomatable; +} + +void PluginDescriptor::SetEffectFamily(const wxString & family) +{ + mEffectFamily = family; +} + +void PluginDescriptor::SetEffectType(EffectType type) +{ + mEffectType = type; +} + +void PluginDescriptor::SetEffectInteractive(bool interactive) +{ + mEffectInteractive = interactive; +} + +void PluginDescriptor::SetEffectDefault(bool dflt) +{ + mEffectDefault = dflt; +} + +void PluginDescriptor::SetEffectLegacy(bool legacy) +{ + mEffectLegacy = legacy; +} + +void PluginDescriptor::SetEffectRealtime(bool realtime) +{ + mEffectRealtime = realtime; +} + +void PluginDescriptor::SetEffectAutomatable(bool automatable) +{ + mEffectAutomatable = automatable; +} + +// Importer + +const wxString & PluginDescriptor::GetImporterIdentifier() const +{ + return mImporterIdentifier; +} + +void PluginDescriptor::SetImporterIdentifier(const wxString & identifier) +{ + mImporterIdentifier = identifier; +} + +const FileExtensions & PluginDescriptor::GetImporterExtensions() + const +{ + return mImporterExtensions; +} + +void PluginDescriptor::SetImporterExtensions( FileExtensions extensions ) +{ + mImporterExtensions = std::move( extensions ); +} + +/////////////////////////////////////////////////////////////////////////////// +// +// PluginManager +// +/////////////////////////////////////////////////////////////////////////////// + +// Registry has the list of plug ins +#define REGVERKEY wxString(wxT("/pluginregistryversion")) +#define REGVERCUR wxString(wxT("1.1")) +#define REGROOT wxString(wxT("/pluginregistry/")) + +// Settings has the values of the plug in settings. +#define SETVERKEY wxString(wxT("/pluginsettingsversion")) +#define SETVERCUR wxString(wxT("1.0")) +#define SETROOT wxString(wxT("/pluginsettings/")) + +#define KEY_ID wxT("ID") +#define KEY_PATH wxT("Path") +#define KEY_SYMBOL wxT("Symbol") +#define KEY_NAME wxT("Name") +#define KEY_VENDOR wxT("Vendor") +#define KEY_VERSION wxT("Version") +#define KEY_DESCRIPTION wxT("Description") +#define KEY_LASTUPDATED wxT("LastUpdated") +#define KEY_ENABLED wxT("Enabled") +#define KEY_VALID wxT("Valid") +#define KEY_PROVIDERID wxT("ProviderID") +#define KEY_EFFECTTYPE wxT("EffectType") +#define KEY_EFFECTFAMILY wxT("EffectFamily") +#define KEY_EFFECTDEFAULT wxT("EffectDefault") +#define KEY_EFFECTINTERACTIVE wxT("EffectInteractive") +#define KEY_EFFECTREALTIME wxT("EffectRealtime") +#define KEY_EFFECTAUTOMATABLE wxT("EffectAutomatable") +#define KEY_EFFECTTYPE_NONE wxT("None") +#define KEY_EFFECTTYPE_ANALYZE wxT("Analyze") +#define KEY_EFFECTTYPE_GENERATE wxT("Generate") +#define KEY_EFFECTTYPE_PROCESS wxT("Process") +#define KEY_EFFECTTYPE_TOOL wxT("Tool") +#define KEY_EFFECTTYPE_HIDDEN wxT("Hidden") +#define KEY_IMPORTERIDENT wxT("ImporterIdent") +//#define KEY_IMPORTERFILTER wxT("ImporterFilter") +#define KEY_IMPORTEREXTENSIONS wxT("ImporterExtensions") + +// ============================================================================ +// +// PluginManagerInterface implementation +// +// ============================================================================ + +const PluginID &PluginManagerInterface::DefaultRegistrationCallback( + ModuleInterface *provider, ComponentInterface *pInterface ) +{ + EffectDefinitionInterface * pEInterface = dynamic_cast(pInterface); + if( pEInterface ) + return PluginManager::Get().RegisterPlugin(provider, pEInterface, PluginTypeEffect); + ComponentInterface * pCInterface = dynamic_cast(pInterface); + if( pCInterface ) + return PluginManager::Get().RegisterPlugin(provider, pCInterface); + static wxString empty; + return empty; +} + +const PluginID &PluginManagerInterface::AudacityCommandRegistrationCallback( + ModuleInterface *provider, ComponentInterface *pInterface ) +{ + ComponentInterface * pCInterface = dynamic_cast(pInterface); + if( pCInterface ) + return PluginManager::Get().RegisterPlugin(provider, pCInterface); + static wxString empty; + return empty; +} + +RegistryPath PluginManager::GetPluginEnabledSetting( const PluginID &ID ) const +{ + auto pPlugin = GetPlugin( ID ); + if ( pPlugin ) + return GetPluginEnabledSetting( *pPlugin ); + return {}; +} + +RegistryPath PluginManager::GetPluginEnabledSetting( + const PluginDescriptor &desc ) const +{ + switch ( desc.GetPluginType() ) { + case PluginTypeModule: { + // Retrieve optional family symbol that was recorded in + // RegisterPlugin() for the module + auto family = desc.GetEffectFamily(); + if ( family.empty() ) // as for built-in effect and command modules + return {}; + else + return wxT('/') + family + wxT("/Enable"); + } + case PluginTypeEffect: + // do NOT use GetEffectFamily() for this descriptor, but instead, + // delegate to the plugin descriptor of the provider, which may + // be different (may be empty) + return GetPluginEnabledSetting( desc.GetProviderID() ); + default: + return {}; + } +} + +bool PluginManager::IsPluginRegistered( + const PluginPath &path, const TranslatableString *pName) +{ + for (auto &pair : mPlugins) { + if (auto &descriptor = pair.second; descriptor.GetPath() == path) { + if (pName) + descriptor.SetSymbol( + { descriptor.GetSymbol().Internal(), *pName }); + return true; + } + } + return false; +} + +const PluginID & PluginManager::RegisterPlugin(ModuleInterface *module) +{ + PluginDescriptor & plug = CreatePlugin(GetID(module), module, PluginTypeModule); + plug.SetEffectFamily(module->GetOptionalFamilySymbol().Internal()); + + plug.SetEnabled(true); + plug.SetValid(true); + + return plug.GetID(); +} + +const PluginID & PluginManager::RegisterPlugin(ModuleInterface *provider, ComponentInterface *command) +{ + PluginDescriptor & plug = CreatePlugin(GetID(command), command, (PluginType)PluginTypeAudacityCommand); + + plug.SetProviderID(PluginManager::GetID(provider)); + + plug.SetEnabled(true); + plug.SetValid(true); + + return plug.GetID(); +} + +const PluginID & PluginManager::RegisterPlugin(ModuleInterface *provider, EffectDefinitionInterface *effect, int type) +{ + PluginDescriptor & plug = CreatePlugin(GetID(effect), effect, (PluginType)type); + + plug.SetProviderID(PluginManager::GetID(provider)); + + plug.SetEffectType(effect->GetClassification()); + plug.SetEffectFamily(effect->GetFamily().Internal()); + plug.SetEffectInteractive(effect->IsInteractive()); + plug.SetEffectDefault(effect->IsDefault()); + plug.SetEffectRealtime(effect->SupportsRealtime()); + plug.SetEffectAutomatable(effect->SupportsAutomation()); + + plug.SetEnabled(true); + plug.SetValid(true); + + return plug.GetID(); +} + +const PluginID & PluginManager::RegisterPlugin(ModuleInterface *provider, ImporterInterface *importer) +{ + PluginDescriptor & plug = CreatePlugin(GetID(importer), importer, PluginTypeImporter); + + plug.SetProviderID(PluginManager::GetID(provider)); + + plug.SetImporterIdentifier(importer->GetPluginStringID()); + plug.SetImporterExtensions(importer->GetSupportedExtensions()); + + return plug.GetID(); +} + +void PluginManager::FindFilesInPathList(const wxString & pattern, + const FilePaths & pathList, + FilePaths & files, + bool directories) +{ + + wxLogNull nolog; + + // Why bother... + if (pattern.empty()) + { + return; + } + + // TODO: We REALLY need to figure out the "Audacity" plug-in path(s) + + FilePaths paths; + + // Add the "per-user" plug-ins directory + { + const wxFileName &ff = FileNames::PlugInDir(); + paths.push_back(ff.GetFullPath()); + } + + // Add the "Audacity" plug-ins directory + wxFileName ff = PlatformCompatibility::GetExecutablePath(); +#if defined(__WXMAC__) + // Path ends for example in "Audacity.app/Contents/MacOSX" + //ff.RemoveLastDir(); + //ff.RemoveLastDir(); + // just remove the MacOSX part. + ff.RemoveLastDir(); +#endif + ff.AppendDir(wxT("plug-ins")); + paths.push_back(ff.GetPath()); + + // Weed out duplicates + for (const auto &filePath : pathList) + { + ff = filePath; + const wxString path{ ff.GetFullPath() }; + if (paths.Index(path, wxFileName::IsCaseSensitive()) == wxNOT_FOUND) + { + paths.push_back(path); + } + } + + // Find all matching files in each path + for (size_t i = 0, cnt = paths.size(); i < cnt; i++) + { + ff = paths[i] + wxFILE_SEP_PATH + pattern; + wxDir::GetAllFiles(ff.GetPath(), &files, ff.GetFullName(), directories ? wxDIR_DEFAULT : wxDIR_FILES); + } + + return; +} + +bool PluginManager::HasSharedConfigGroup(const PluginID & ID, const RegistryPath & group) +{ + return HasGroup(SharedGroup(ID, group)); +} + +bool PluginManager::GetSharedConfigSubgroups(const PluginID & ID, const RegistryPath & group, RegistryPaths & subgroups) +{ + return GetSubgroups(SharedGroup(ID, group), subgroups); +} + +bool PluginManager::GetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, wxString & value, const wxString & defval) +{ + return GetConfig(SharedKey(ID, group, key), value, defval); +} + +bool PluginManager::GetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, int & value, int defval) +{ + return GetConfig(SharedKey(ID, group, key), value, defval); +} + +bool PluginManager::GetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, bool & value, bool defval) +{ + return GetConfig(SharedKey(ID, group, key), value, defval); +} + +bool PluginManager::GetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, float & value, float defval) +{ + return GetConfig(SharedKey(ID, group, key), value, defval); +} + +bool PluginManager::GetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, double & value, double defval) +{ + return GetConfig(SharedKey(ID, group, key), value, defval); +} + +bool PluginManager::SetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const wxString & value) +{ + return SetConfig(SharedKey(ID, group, key), value); +} + +bool PluginManager::SetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const int & value) +{ + return SetConfig(SharedKey(ID, group, key), value); +} + +bool PluginManager::SetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const bool & value) +{ + return SetConfig(SharedKey(ID, group, key), value); +} + +bool PluginManager::SetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const float & value) +{ + return SetConfig(SharedKey(ID, group, key), value); +} + +bool PluginManager::SetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const double & value) +{ + return SetConfig(SharedKey(ID, group, key), value); +} + +bool PluginManager::RemoveSharedConfigSubgroup(const PluginID & ID, const RegistryPath & group) +{ + bool result = GetSettings()->DeleteGroup(SharedGroup(ID, group)); + if (result) + { + GetSettings()->Flush(); + } + + return result; +} + +bool PluginManager::RemoveSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key) +{ + bool result = GetSettings()->DeleteEntry(SharedKey(ID, group, key)); + if (result) + { + GetSettings()->Flush(); + } + + return result; +} + +bool PluginManager::HasPrivateConfigGroup(const PluginID & ID, const RegistryPath & group) +{ + return HasGroup(PrivateGroup(ID, group)); +} + +bool PluginManager::GetPrivateConfigSubgroups(const PluginID & ID, const RegistryPath & group, RegistryPaths & subgroups) +{ + return GetSubgroups(PrivateGroup(ID, group), subgroups); +} + +bool PluginManager::GetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, wxString & value, const wxString & defval) +{ + return GetConfig(PrivateKey(ID, group, key), value, defval); +} + +bool PluginManager::GetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, int & value, int defval) +{ + return GetConfig(PrivateKey(ID, group, key), value, defval); +} + +bool PluginManager::GetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, bool & value, bool defval) +{ + return GetConfig(PrivateKey(ID, group, key), value, defval); +} + +bool PluginManager::GetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, float & value, float defval) +{ + return GetConfig(PrivateKey(ID, group, key), value, defval); +} + +bool PluginManager::GetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, double & value, double defval) +{ + return GetConfig(PrivateKey(ID, group, key), value, defval); +} + +bool PluginManager::SetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const wxString & value) +{ + return SetConfig(PrivateKey(ID, group, key), value); +} + +bool PluginManager::SetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const int & value) +{ + return SetConfig(PrivateKey(ID, group, key), value); +} + +bool PluginManager::SetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const bool & value) +{ + return SetConfig(PrivateKey(ID, group, key), value); +} + +bool PluginManager::SetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const float & value) +{ + return SetConfig(PrivateKey(ID, group, key), value); +} + +bool PluginManager::SetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const double & value) +{ + return SetConfig(PrivateKey(ID, group, key), value); +} + +bool PluginManager::RemovePrivateConfigSubgroup(const PluginID & ID, const RegistryPath & group) +{ + bool result = GetSettings()->DeleteGroup(PrivateGroup(ID, group)); + if (result) + { + GetSettings()->Flush(); + } + + return result; +} + +bool PluginManager::RemovePrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key) +{ + bool result = GetSettings()->DeleteEntry(PrivateKey(ID, group, key)); + if (result) + { + GetSettings()->Flush(); + } + + return result; +} + +// ============================================================================ +// +// PluginManager +// +// ============================================================================ + +// The one and only PluginManager +std::unique_ptr PluginManager::mInstance{}; + +// ---------------------------------------------------------------------------- +// Creation/Destruction +// ---------------------------------------------------------------------------- + +PluginManager::PluginManager() +{ + mSettings = NULL; +} + +PluginManager::~PluginManager() +{ + // Ensure termination (harmless if already done) + Terminate(); +} + +// ---------------------------------------------------------------------------- +// PluginManager implementation +// ---------------------------------------------------------------------------- + +// ============================================================================ +// +// Return reference to singleton +// +// (Thread-safe...no active threading during construction or after destruction) +// ============================================================================ + +PluginManager & PluginManager::Get() +{ + if (!mInstance) + { + mInstance.reset(safenew PluginManager); + } + + return *mInstance; +} + +void PluginManager::Initialize() +{ + // Always load the registry first + Load(); + + // And force load of setting to verify it's accessible + GetSettings(); + + // Then look for providers (they may autoregister plugins) + ModuleManager::Get().DiscoverProviders(); + + // And finally check for updates +#ifndef EXPERIMENTAL_EFFECT_MANAGEMENT + CheckForUpdates(); +#else + const bool kFast = true; + CheckForUpdates( kFast ); +#endif +} + +void PluginManager::Terminate() +{ + // Get rid of all non-module plugins first + PluginMap::iterator iter = mPlugins.begin(); + while (iter != mPlugins.end()) + { + PluginDescriptor & plug = iter->second; + if (plug.GetPluginType() == PluginTypeEffect) + { + mPlugins.erase(iter++); + continue; + } + + ++iter; + } + + // Now get rid of the modules + iter = mPlugins.begin(); + while (iter != mPlugins.end()) + { + mPlugins.erase(iter++); + } +} + +bool PluginManager::DropFile(const wxString &fileName) +{ + auto &mm = ModuleManager::Get(); + const wxFileName src{ fileName }; + + for (auto &plug : PluginsOfType(PluginTypeModule)) { + auto module = static_cast + (mm.CreateProviderInstance(plug.GetID(), plug.GetPath())); + if (! module) + continue; + + const auto &ff = module->InstallPath(); + const auto &extensions = module->GetFileExtensions(); + if ( !ff.empty() && + extensions.Index(src.GetExt(), false) != wxNOT_FOUND ) { + TranslatableString errMsg; + // Do dry-run test of the file format + unsigned nPlugIns = + module->DiscoverPluginsAtPath(fileName, errMsg, {}); + if (nPlugIns) { + // File contents are good for this module, so check no others. + // All branches of this block return true, even in case of + // failure for other reasons, to signal that other drag-and-drop + // actions should not be tried. + + // Find path to copy it + wxFileName dst; + dst.AssignDir( ff ); + dst.SetFullName( src.GetFullName() ); + if ( dst.Exists() ) { + // Query whether to overwrite + bool overwrite = (wxYES == ::AudacityMessageBox( + XO("Overwrite the plug-in file %s?") + .Format( dst.GetFullPath() ), + XO("Plug-in already exists"), + wxYES_NO ) ); + if ( !overwrite ) + return true; + } + + // Move the file or subtree + bool copied = false; + auto dstPath = dst.GetFullPath(); + if ( src.FileExists() ) + // A simple one-file plug-in + copied = FileNames::DoCopyFile( + src.GetFullPath(), dstPath, true ); + else { + // A sub-folder + // such as for some VST packages + // Recursive copy needed -- to do + return true; + } + + if (!copied) { + ::AudacityMessageBox( + XO("Plug-in file is in use. Failed to overwrite") ); + return true; + } + + // Register for real + std::vector ids; + std::vector names; + nPlugIns = module->DiscoverPluginsAtPath(dstPath, errMsg, + [&](ModuleInterface *provider, ComponentInterface *ident) + -> const PluginID& { + // Register as by default, but also collecting the PluginIDs + // and names + auto &id = PluginManagerInterface::DefaultRegistrationCallback( + provider, ident); + ids.push_back(id); + names.push_back( ident->GetSymbol().Translation() ); + return id; + }); + if ( ! nPlugIns ) { + // Unlikely after the dry run succeeded + ::AudacityMessageBox( + XO("Failed to register:\n%s").Format( errMsg ) ); + return true; + } + + // Ask whether to enable the plug-ins + if (auto nIds = ids.size()) { + auto message = XPC( + /* i18n-hint A plug-in is an optional added program for a sound + effect, or generator, or analyzer */ + "Enable this plug-in?\n", + "Enable these plug-ins?\n", + 0, + "plug-ins" + )( nIds ); + for (const auto &name : names) + message.Join( Verbatim( name ), wxT("\n") ); + bool enable = (wxYES == ::AudacityMessageBox( + message, + XO("Enable new plug-ins"), + wxYES_NO ) ); + for (const auto &id : ids) + mPlugins[id].SetEnabled(enable); + // Make changes to enabled status persist: + this->Save(); + } + + return true; + } + } + } + + return false; +} + +void PluginManager::Load() +{ + // Create/Open the registry + auto pRegistry = AudacityFileConfig::Create( + {}, {}, FileNames::PluginRegistry()); + auto ®istry = *pRegistry; + + // If this group doesn't exist then we have something that's not a registry. + // We should probably warn the user, but it's pretty unlikely that this will happen. + if (!registry.HasGroup(REGROOT)) + { + // Must start over + registry.DeleteAll(); + registry.Flush(); + return; + } + + // Check for a registry version that we can understand + // TODO: Should also check for a registry file that is newer than + // what we can understand. + wxString regver = registry.Read(REGVERKEY); + if (regver < REGVERCUR ) + { + // Conversion code here, for when registry version changes. + + // We iterate through the effects, possibly updating their info. + wxString groupName; + long groupIndex; + wxString group = GetPluginTypeString(PluginTypeEffect); + wxString cfgPath = REGROOT + group + wxCONFIG_PATH_SEPARATOR; + wxArrayString groupsToDelete; + + registry.SetPath(cfgPath); + for (bool cont = registry.GetFirstGroup(groupName, groupIndex); + cont; + registry.SetPath(cfgPath), + cont = registry.GetNextGroup(groupName, groupIndex)) + { + registry.SetPath(groupName); + wxString effectSymbol = registry.Read(KEY_SYMBOL, ""); + wxString effectVersion = registry.Read(KEY_VERSION, ""); + + + // For 2.3.0 the plugins we distribute have moved around. + // So we upped the registry version number to 1.1. + // These particular config edits were originally written to fix Bug 1914. + if (regver <= "1.0") { + // Nyquist prompt is a built-in that has moved to the tools menu. + if (effectSymbol == NYQUIST_PROMPT_ID) { + registry.Write(KEY_EFFECTTYPE, "Tool"); + // Old version of SDE was in Analyze menu. Now it is in Tools. + // We don't want both the old and the new. + } else if ((effectSymbol == "Sample Data Export") && (effectVersion == "n/a")) { + groupsToDelete.push_back(cfgPath + groupName); + // Old version of SDI was in Generate menu. Now it is in Tools. + } else if ((effectSymbol == "Sample Data Import") && (effectVersion == "n/a")) { + groupsToDelete.push_back(cfgPath + groupName); + } + } + + } + // Doing the deletion within the search loop risked skipping some items, + // hence the delayed delete. + for (unsigned int i = 0; i < groupsToDelete.size(); i++) { + registry.DeleteGroup(groupsToDelete[i]); + } + registry.SetPath(""); + registry.Write(REGVERKEY, REGVERCUR); + // Updates done. Make sure we read the updated data later. + registry.Flush(); + } + + // Load all provider plugins first + LoadGroup(®istry, PluginTypeModule); + + // Now the rest + LoadGroup(®istry, PluginTypeEffect); + LoadGroup(®istry, PluginTypeAudacityCommand ); + LoadGroup(®istry, PluginTypeExporter); + LoadGroup(®istry, PluginTypeImporter); + + LoadGroup(®istry, PluginTypeStub); + return; +} + +void PluginManager::LoadGroup(FileConfig *pRegistry, PluginType type) +{ +#ifdef __WXMAC__ + // Bug 1590: On Mac, we should purge the registry of Nyquist plug-ins + // bundled with other versions of Audacity, assuming both versions + // were properly installed in /Applications (or whatever it is called in + // your locale) + + const auto fullExePath = PlatformCompatibility::GetExecutablePath(); + + // Strip rightmost path components up to *.app + wxFileName exeFn{ fullExePath }; + exeFn.SetEmptyExt(); + exeFn.SetName(wxString{}); + while(exeFn.GetDirCount() && !exeFn.GetDirs().back().EndsWith(".app")) + exeFn.RemoveLastDir(); + + const auto goodPath = exeFn.GetPath(); + + if(exeFn.GetDirCount()) + exeFn.RemoveLastDir(); + const auto possiblyBadPath = exeFn.GetPath(); + + auto AcceptPath = [&](const wxString &path) { + if (!path.StartsWith(possiblyBadPath)) + // Assume it's not under /Applications + return true; + if (path.StartsWith(goodPath)) + // It's bundled with this executable + return true; + return false; + }; +#else + auto AcceptPath = [](const wxString&){ return true; }; +#endif + + wxString strVal; + bool boolVal; + wxString groupName; + long groupIndex; + wxString group = GetPluginTypeString(type); + wxString cfgPath = REGROOT + group + wxCONFIG_PATH_SEPARATOR; + + pRegistry->SetPath(cfgPath); + for (bool cont = pRegistry->GetFirstGroup(groupName, groupIndex); + cont; + pRegistry->SetPath(cfgPath), + cont = pRegistry->GetNextGroup(groupName, groupIndex)) + { + PluginDescriptor plug; + + pRegistry->SetPath(groupName); + + groupName = ConvertID(groupName); + + // Bypass group if the ID is already in use + if (mPlugins.count(groupName)) + continue; + + // Set the ID and type + plug.SetID(groupName); + plug.SetPluginType(type); + + // Get the provider ID and bypass group if not found + if (!pRegistry->Read(KEY_PROVIDERID, &strVal, wxEmptyString)) + { + // Bypass group if the provider isn't valid + if (!strVal.empty() && !mPlugins.count(strVal)) + continue; + } + plug.SetProviderID(PluginID(strVal)); + + // Get the path (optional) + pRegistry->Read(KEY_PATH, &strVal, wxEmptyString); + if (!AcceptPath(strVal)) + // Ignore the obsolete path in the config file, during session, + // but don't remove it from the file. Maybe you really want to + // switch back to the other version of Audacity and lose nothing. + continue; + plug.SetPath(strVal); + + /* + // PRL: Ignore names written in configs before 2.3.0! + // use Internal string only! Let the present version of Audacity map + // that to a user-visible string. + // Get the name and bypass group if not found + if (!pRegistry->Read(KEY_NAME, &strVal)) + { + continue; + } + plug.SetName(strVal); + */ + + // Get the symbol...Audacity 2.3.0 or later requires it + // bypass group if not found + // Note, KEY_SYMBOL started getting written to config files in 2.1.0. + // KEY_NAME (now ignored) was written before that, but only for VST + // effects. + if (!pRegistry->Read(KEY_SYMBOL, &strVal)) + continue; + + // Related to Bug2778: config file only remembered an internal name, + // so this symbol may not contain the correct TranslatableString. + // See calls to IsPluginRegistered which can correct that. + plug.SetSymbol(strVal); + + // Get the version and bypass group if not found + if (!pRegistry->Read(KEY_VERSION, &strVal)) + { + continue; + } + plug.SetVersion(strVal); + + // Get the vendor and bypass group if not found + if (!pRegistry->Read(KEY_VENDOR, &strVal)) + { + continue; + } + plug.SetVendor( strVal ); + +#if 0 + // This was done before version 2.2.2, but the value was not really used + // But absence of a value will cause early versions to skip the group + // Therefore we still write a blank to keep pluginregistry.cfg + // backwards-compatible + + // Get the description and bypass group if not found + if (!pRegistry->Read(KEY_DESCRIPTION, &strVal)) + { + continue; + } +#endif + + // Is it enabled...default to no if not found + pRegistry->Read(KEY_ENABLED, &boolVal, false); + plug.SetEnabled(boolVal); + + // Is it valid...default to no if not found + pRegistry->Read(KEY_VALID, &boolVal, false); + plug.SetValid(boolVal); + + switch (type) + { + case PluginTypeModule: + { + // Nothing to do here yet + } + break; + + case PluginTypeEffect: + { + // Get the effect type and bypass group if not found + if (!pRegistry->Read(KEY_EFFECTTYPE, &strVal)) + continue; + + if (strVal == KEY_EFFECTTYPE_NONE) + plug.SetEffectType(EffectTypeNone); + else if (strVal == KEY_EFFECTTYPE_ANALYZE) + plug.SetEffectType(EffectTypeAnalyze); + else if (strVal == KEY_EFFECTTYPE_GENERATE) + plug.SetEffectType(EffectTypeGenerate); + else if (strVal == KEY_EFFECTTYPE_PROCESS) + plug.SetEffectType(EffectTypeProcess); + else if (strVal == KEY_EFFECTTYPE_TOOL) + plug.SetEffectType(EffectTypeTool); + else if (strVal == KEY_EFFECTTYPE_HIDDEN) + plug.SetEffectType(EffectTypeHidden); + else + continue; + + // Get the effect family and bypass group if not found + if (!pRegistry->Read(KEY_EFFECTFAMILY, &strVal)) + { + continue; + } + plug.SetEffectFamily(strVal); + + // Is it a default (above the line) effect and bypass group if not found + if (!pRegistry->Read(KEY_EFFECTDEFAULT, &boolVal)) + { + continue; + } + plug.SetEffectDefault(boolVal); + + // Is it an interactive effect and bypass group if not found + if (!pRegistry->Read(KEY_EFFECTINTERACTIVE, &boolVal)) + { + continue; + } + plug.SetEffectInteractive(boolVal); + + // Is it a realtime capable effect and bypass group if not found + if (!pRegistry->Read(KEY_EFFECTREALTIME, &boolVal)) + { + continue; + } + plug.SetEffectRealtime(boolVal); + + // Does the effect support automation...bypass group if not found + if (!pRegistry->Read(KEY_EFFECTAUTOMATABLE, &boolVal)) + { + continue; + } + plug.SetEffectAutomatable(boolVal); + } + break; + + case PluginTypeImporter: + { + // Get the importer identifier and bypass group if not found + if (!pRegistry->Read(KEY_IMPORTERIDENT, &strVal)) + { + continue; + } + plug.SetImporterIdentifier(strVal); + + // Get the importer extensions and bypass group if not found + if (!pRegistry->Read(KEY_IMPORTEREXTENSIONS, &strVal)) + { + continue; + } + FileExtensions extensions; + wxStringTokenizer tkr(strVal, wxT(":")); + while (tkr.HasMoreTokens()) + { + extensions.push_back(tkr.GetNextToken()); + } + plug.SetImporterExtensions(extensions); + } + break; + + case PluginTypeStub: + { + // Nothing additional for stubs + } + break; + + // Not used by 2.1.1 or greater and should be removed after a few releases past 2.1.0. + case PluginTypeNone: + { + // Used for stub groups + } + break; + + default: + { + continue; + } + } + + // Everything checked out...accept the plugin + mPlugins[groupName] = std::move(plug); + } + + return; +} + +void PluginManager::Save() +{ + // Create/Open the registry + auto pRegistry = AudacityFileConfig::Create( + {}, {}, FileNames::PluginRegistry()); + auto ®istry = *pRegistry; + + // Clear it out + registry.DeleteAll(); + + // Write the version string + registry.Write(REGVERKEY, REGVERCUR); + + // Save the individual groups + SaveGroup(®istry, PluginTypeEffect); + SaveGroup(®istry, PluginTypeExporter); + SaveGroup(®istry, PluginTypeAudacityCommand); + SaveGroup(®istry, PluginTypeImporter); + SaveGroup(®istry, PluginTypeStub); + + // Not used by 2.1.1 or greater, but must save to allow users to switch between 2.1.0 + // and 2.1.1+. This should be removed after a few releases past 2.1.0. + //SaveGroup(®istry, PluginTypeNone); + + // And now the providers + SaveGroup(®istry, PluginTypeModule); + + // Just to be safe + registry.Flush(); +} + +void PluginManager::SaveGroup(FileConfig *pRegistry, PluginType type) +{ + wxString group = GetPluginTypeString(type); + for (auto &pair : mPlugins) { + auto & plug = pair.second; + + if (plug.GetPluginType() != type) + { + continue; + } + + pRegistry->SetPath(REGROOT + group + wxCONFIG_PATH_SEPARATOR + ConvertID(plug.GetID())); + + pRegistry->Write(KEY_PATH, plug.GetPath()); + + // See comments with the corresponding load-time call to SetSymbol(). + pRegistry->Write(KEY_SYMBOL, plug.GetSymbol().Internal()); + + // PRL: Writing KEY_NAME which is no longer read, but older Audacity + // versions expect to find it. + pRegistry->Write(KEY_NAME, plug.GetSymbol().Msgid().MSGID()); + + pRegistry->Write(KEY_VERSION, plug.GetUntranslatedVersion()); + pRegistry->Write(KEY_VENDOR, plug.GetVendor()); + // Write a blank -- see comments in LoadGroup: + pRegistry->Write(KEY_DESCRIPTION, wxString{}); + pRegistry->Write(KEY_PROVIDERID, plug.GetProviderID()); + pRegistry->Write(KEY_ENABLED, plug.IsEnabled()); + pRegistry->Write(KEY_VALID, plug.IsValid()); + + switch (type) + { + case PluginTypeModule: + break; + + case PluginTypeEffect: + { + EffectType etype = plug.GetEffectType(); + wxString stype; + if (etype == EffectTypeNone) + stype = KEY_EFFECTTYPE_NONE; + else if (etype == EffectTypeAnalyze) + stype = KEY_EFFECTTYPE_ANALYZE; + else if (etype == EffectTypeGenerate) + stype = KEY_EFFECTTYPE_GENERATE; + else if (etype == EffectTypeProcess) + stype = KEY_EFFECTTYPE_PROCESS; + else if (etype == EffectTypeTool) + stype = KEY_EFFECTTYPE_TOOL; + else if (etype == EffectTypeHidden) + stype = KEY_EFFECTTYPE_HIDDEN; + + pRegistry->Write(KEY_EFFECTTYPE, stype); + pRegistry->Write(KEY_EFFECTFAMILY, plug.GetEffectFamily()); + pRegistry->Write(KEY_EFFECTDEFAULT, plug.IsEffectDefault()); + pRegistry->Write(KEY_EFFECTINTERACTIVE, plug.IsEffectInteractive()); + pRegistry->Write(KEY_EFFECTREALTIME, plug.IsEffectRealtime()); + pRegistry->Write(KEY_EFFECTAUTOMATABLE, plug.IsEffectAutomatable()); + } + break; + + case PluginTypeImporter: + { + pRegistry->Write(KEY_IMPORTERIDENT, plug.GetImporterIdentifier()); + const auto & extensions = plug.GetImporterExtensions(); + wxString strExt; + for (size_t i = 0, cnt = extensions.size(); i < cnt; i++) + { + strExt += extensions[i] + wxT(":"); + } + strExt.RemoveLast(1); + pRegistry->Write(KEY_IMPORTEREXTENSIONS, strExt); + } + break; + + default: + break; + } + } + + return; +} + +// If bFast is true, do not do a full check. Just check the ones +// that are quick to check. Currently (Feb 2017) just Nyquist +// and built-ins. +void PluginManager::CheckForUpdates(bool bFast) +{ + ModuleManager & mm = ModuleManager::Get(); + wxArrayString pathIndex; + for (auto &pair : mPlugins) { + auto &plug = pair.second; + + // Bypass 2.1.0 placeholders...remove this after a few releases past 2.1.0 + if (plug.GetPluginType() != PluginTypeNone) + pathIndex.push_back(plug.GetPath().BeforeFirst(wxT(';'))); + } + + // Check all known plugins to ensure they are still valid and scan for NEW ones. + // + // All NEW plugins get a stub entry created that will remain in place until the + // user enables or disables the plugin. + // + // Because we use the plugins "path" as returned by the providers, we can actually + // have multiple providers report the same path since, at this point, they only + // know that the path might possibly be one supported by the provider. + // + // When the user enables the plugin, each provider that reported it will be asked + // to register the plugin. + for (auto &pair : mPlugins) { + auto &plug = pair.second; + const PluginID & plugID = plug.GetID(); + const wxString & plugPath = plug.GetPath(); + PluginType plugType = plug.GetPluginType(); + + // Bypass 2.1.0 placeholders...remove this after a few releases past 2.1.0 + if (plugType == PluginTypeNone) + { + continue; + } + + if ( plugType == PluginTypeModule ) + { + if( bFast ) + { + // Skip modules, when doing a fast refresh/check. + } + else if (!mm.IsProviderValid(plugID, plugPath)) + { + plug.SetEnabled(false); + plug.SetValid(false); + } + else + { + // Collect plugin paths + auto paths = mm.FindPluginsForProvider(plugID, plugPath); + for (size_t i = 0, cnt = paths.size(); i < cnt; i++) + { + wxString path = paths[i].BeforeFirst(wxT(';'));; + if ( ! make_iterator_range( pathIndex ).contains( path ) ) + { + PluginID ID = plugID + wxT("_") + path; + PluginDescriptor & plug2 = mPlugins[ID]; // This will create a NEW descriptor + plug2.SetPluginType(PluginTypeStub); + plug2.SetID(ID); + plug2.SetProviderID(plugID); + plug2.SetPath(path); + plug2.SetEnabled(false); + plug2.SetValid(false); + } + } + } + } + else if (plugType != PluginTypeNone && plugType != PluginTypeStub) + { + plug.SetValid(mm.IsPluginValid(plug.GetProviderID(), plugPath, bFast)); + if (!plug.IsValid()) + { + plug.SetEnabled(false); + } + } + } + + Save(); + + return; +} + +bool PluginManager::ShowManager(wxWindow *parent, EffectType type) +{ + CheckForUpdates(); + + PluginRegistrationDialog dlg(parent, type); + return dlg.ShowModal() == wxID_OK; +} + +// Here solely for the purpose of Nyquist Workbench until +// a better solution is devised. +const PluginID & PluginManager::RegisterPlugin( + std::unique_ptr effect, PluginType type) +{ + PluginDescriptor & plug = + CreatePlugin(GetID(effect.get()), effect.get(), type); + + plug.SetEffectType(effect->GetType()); + plug.SetEffectFamily(effect->GetFamily().Internal()); + plug.SetEffectInteractive(effect->IsInteractive()); + plug.SetEffectDefault(effect->IsDefault()); + plug.SetEffectRealtime(effect->SupportsRealtime()); + plug.SetEffectAutomatable(effect->SupportsAutomation()); + + plug.SetInstance(std::move(effect)); + plug.SetEffectLegacy(true); + plug.SetEnabled(true); + plug.SetValid(true); + + return plug.GetID(); +} + +void PluginManager::UnregisterPlugin(const PluginID & ID) +{ + mPlugins.erase(ID); +} + +int PluginManager::GetPluginCount(PluginType type) +{ + return count_if(mPlugins.begin(), mPlugins.end(), [type](auto &pair){ + return pair.second.GetPluginType() == type; }); +} + +const PluginDescriptor *PluginManager::GetPlugin(const PluginID & ID) const +{ + if (auto iter = mPlugins.find(ID); iter == mPlugins.end()) + return nullptr; + else + return &iter->second; +} + +void PluginManager::Iterator::Advance(bool incrementing) +{ + const auto end = mPm.mPlugins.end(); + if (incrementing && mIterator != end) + ++mIterator; + bool all = mPluginType == PluginTypeNone && mEffectType == EffectTypeNone; + for (; mIterator != end; ++mIterator) { + auto &plug = mIterator->second; + if (!all && !(plug.IsValid() && plug.IsEnabled())) + continue; + auto plugType = plug.GetPluginType(); + if ((mPluginType == PluginTypeNone || (plugType & mPluginType)) && + (mEffectType == EffectTypeNone || plug.GetEffectType() == mEffectType)) { + if (!all && (plugType & PluginTypeEffect)) { + // This preference may be written by EffectsPrefs + auto setting = mPm.GetPluginEnabledSetting( plug ); + if (!(setting.empty() || gPrefs->Read( setting, true ))) + continue; + } + // Pause iteration at this match + break; + } + } +} + +PluginManager::Iterator::Iterator(PluginManager &manager) +: mPm{ manager } +, mIterator{ manager.mPlugins.begin() } +{ +} + +PluginManager::Iterator::Iterator(PluginManager &manager, int type) +: mPm{ manager } +, mIterator{ manager.mPlugins.begin() } +, mPluginType{ type } +{ + Advance(false); +} + +PluginManager::Iterator::Iterator(PluginManager &manager, EffectType type) +: mPm{ manager } +, mIterator{ manager.mPlugins.begin() } +, mEffectType{ type } +{ + Advance(false); +} + +auto PluginManager::Iterator::operator ++() -> Iterator & +{ + Advance(true); + return *this; +} + +bool PluginManager::IsPluginEnabled(const PluginID & ID) +{ + if (auto iter = mPlugins.find(ID); iter == mPlugins.end()) + return false; + else + return iter->second.IsEnabled(); +} + +void PluginManager::EnablePlugin(const PluginID & ID, bool enable) +{ + if (auto iter = mPlugins.find(ID); iter == mPlugins.end()) + return; + else + iter->second.SetEnabled(enable); +} + +const ComponentInterfaceSymbol & PluginManager::GetSymbol(const PluginID & ID) +{ + if (auto iter = mPlugins.find(ID); iter == mPlugins.end()) { + static ComponentInterfaceSymbol empty; + return empty; + } + else + return iter->second.GetSymbol(); +} + +ComponentInterface *PluginManager::GetInstance(const PluginID & ID) +{ + if (auto iter = mPlugins.find(ID); iter == mPlugins.end()) + return nullptr; + else { + auto &plug = iter->second; + + // If not dealing with legacy effects, make sure the provider is loaded + if (!plug.IsEffectLegacy()) + { + const PluginID & prov = plug.GetProviderID(); + if (auto iter2 = mPlugins.find(prov); iter2 == mPlugins.end()) + return nullptr; + else + iter2->second.GetInstance(); + } + + return plug.GetInstance(); + } +} + +PluginID PluginManager::GetID(ModuleInterface *module) +{ + return wxString::Format(wxT("%s_%s_%s_%s_%s"), + GetPluginTypeString(PluginTypeModule), + wxEmptyString, + module->GetVendor().Internal(), + module->GetSymbol().Internal(), + module->GetPath()); +} + +PluginID PluginManager::GetID(ComponentInterface *command) +{ + return wxString::Format(wxT("%s_%s_%s_%s_%s"), + GetPluginTypeString(PluginTypeAudacityCommand), + wxEmptyString, + command->GetVendor().Internal(), + command->GetSymbol().Internal(), + command->GetPath()); +} + +PluginID PluginManager::GetID(EffectDefinitionInterface *effect) +{ + return wxString::Format(wxT("%s_%s_%s_%s_%s"), + GetPluginTypeString(PluginTypeEffect), + effect->GetFamily().Internal(), + effect->GetVendor().Internal(), + effect->GetSymbol().Internal(), + effect->GetPath()); +} + +PluginID PluginManager::GetID(ImporterInterface *importer) +{ + return wxString::Format(wxT("%s_%s_%s_%s_%s"), + GetPluginTypeString(PluginTypeImporter), + wxEmptyString, + importer->GetVendor().Internal(), + importer->GetSymbol().Internal(), + importer->GetPath()); +} + +// This string persists in configuration files +// So config compatibility will break if it is changed across Audacity versions +wxString PluginManager::GetPluginTypeString(PluginType type) +{ + wxString str; + + switch (type) + { + default: + case PluginTypeNone: + str = wxT("Placeholder"); + break; + case PluginTypeStub: + str = wxT("Stub"); + break; + case PluginTypeEffect: + str = wxT("Effect"); + break; + case PluginTypeAudacityCommand: + str = wxT("Generic"); + break; + case PluginTypeExporter: + str = wxT("Exporter"); + break; + case PluginTypeImporter: + str = wxT("Importer"); + break; + case PluginTypeModule: + str = wxT("Module"); + break; + } + + return str; +} + +PluginDescriptor & PluginManager::CreatePlugin(const PluginID & id, + ComponentInterface *ident, + PluginType type) +{ + // This will either create a NEW entry or replace an existing entry + PluginDescriptor & plug = mPlugins[id]; + + plug.SetPluginType(type); + + plug.SetID(id); + plug.SetPath(ident->GetPath()); + plug.SetSymbol(ident->GetSymbol()); + plug.SetVendor(ident->GetVendor().Internal()); + plug.SetVersion(ident->GetVersion()); + + return plug; +} + +FileConfig *PluginManager::GetSettings() +{ + if (!mSettings) + { + mSettings = + AudacityFileConfig::Create({}, {}, FileNames::PluginSettings()); + + // Check for a settings version that we can understand + if (mSettings->HasEntry(SETVERKEY)) + { + wxString setver = mSettings->Read(SETVERKEY, SETVERKEY); + if (setver < SETVERCUR ) + { + // This is where we'd put in conversion code when the + // settings version changes. + // + // Should also check for a settings file that is newer than + // what we can understand. + } + } + else + { + // Make sure is has a version string + mSettings->Write(SETVERKEY, SETVERCUR); + mSettings->Flush(); + } + } + + return mSettings.get(); +} + +bool PluginManager::HasGroup(const RegistryPath & group) +{ + auto settings = GetSettings(); + + bool res = settings->HasGroup(group); + if (res) + { + // The group exists, but empty groups aren't considered valid + wxString oldPath = settings->GetPath(); + settings->SetPath(group); + res = settings->GetNumberOfEntries() || settings->GetNumberOfGroups(); + settings->SetPath(oldPath); + } + + return res; +} + +bool PluginManager::GetSubgroups(const RegistryPath & group, RegistryPaths & subgroups) +{ + if (group.empty() || !HasGroup(group)) + { + return false; + } + + wxString path = GetSettings()->GetPath(); + GetSettings()->SetPath(group); + + wxString name; + long index = 0; + if (GetSettings()->GetFirstGroup(name, index)) + { + do + { + subgroups.push_back(name); + } while (GetSettings()->GetNextGroup(name, index)); + } + + GetSettings()->SetPath(path); + + return true; +} + +bool PluginManager::GetConfig(const RegistryPath & key, int & value, int defval) +{ + bool result = false; + + if (!key.empty()) + { + result = GetSettings()->Read(key, &value, defval); + } + + return result; +} + +bool PluginManager::GetConfig(const RegistryPath & key, wxString & value, const wxString & defval) +{ + bool result = false; + + if (!key.empty()) + { + wxString wxval; + + result = GetSettings()->Read(key, &wxval, defval); + + value = wxval; + } + + return result; +} + +bool PluginManager::GetConfig(const RegistryPath & key, bool & value, bool defval) +{ + bool result = false; + + if (!key.empty()) + { + result = GetSettings()->Read(key, &value, defval); + } + + return result; +} + +bool PluginManager::GetConfig(const RegistryPath & key, float & value, float defval) +{ + bool result = false; + + if (!key.empty()) + { + double dval = 0.0; + + result = GetSettings()->Read(key, &dval, (double) defval); + + value = (float) dval; + } + + return result; +} + +bool PluginManager::GetConfig(const RegistryPath & key, double & value, double defval) +{ + bool result = false; + + if (!key.empty()) + { + result = GetSettings()->Read(key, &value, defval); + } + + return result; +} + +bool PluginManager::SetConfig(const RegistryPath & key, const wxString & value) +{ + bool result = false; + + if (!key.empty()) + { + wxString wxval = value; + result = GetSettings()->Write(key, wxval); + if (result) + { + result = GetSettings()->Flush(); + } + } + + return result; +} + +bool PluginManager::SetConfig(const RegistryPath & key, const int & value) +{ + bool result = false; + + if (!key.empty()) + { + result = GetSettings()->Write(key, value); + if (result) + { + result = GetSettings()->Flush(); + } + } + + return result; +} + +bool PluginManager::SetConfig(const RegistryPath & key, const bool & value) +{ + bool result = false; + + if (!key.empty()) + { + result = GetSettings()->Write(key, value); + if (result) + { + result = GetSettings()->Flush(); + } + } + + return result; +} + +bool PluginManager::SetConfig(const RegistryPath & key, const float & value) +{ + bool result = false; + + if (!key.empty()) + { + result = GetSettings()->Write(key, value); + if (result) + { + result = GetSettings()->Flush(); + } + } + + return result; +} + +bool PluginManager::SetConfig(const RegistryPath & key, const double & value) +{ + bool result = false; + + if (!key.empty()) + { + result = GetSettings()->Write(key, value); + if (result) + { + result = GetSettings()->Flush(); + } + } + + return result; +} + +/* Return value is a key for lookup in a config file */ +RegistryPath PluginManager::SettingsPath(const PluginID & ID, bool shared) +{ + // All the strings reported by PluginDescriptor and used in this function + // persist in the plugin settings configuration file, so they should not + // be changed across Audacity versions, or else compatibility of the + // configuration files will break. + + if (auto iter = mPlugins.find(ID); iter == mPlugins.end()) + return {}; + else { + const PluginDescriptor & plug = iter->second; + + wxString id = GetPluginTypeString(plug.GetPluginType()) + + wxT("_") + + plug.GetEffectFamily() + // is empty for non-Effects + wxT("_") + + plug.GetVendor() + + wxT("_") + + (shared ? wxString{} : plug.GetSymbol().Internal()); + + return SETROOT + + ConvertID(id) + + wxCONFIG_PATH_SEPARATOR + + (shared ? wxT("shared") : wxT("private")) + + wxCONFIG_PATH_SEPARATOR; + } +} + +/* Return value is a key for lookup in a config file */ +RegistryPath PluginManager::SharedGroup(const PluginID & ID, const RegistryPath & group) +{ + wxString path = SettingsPath(ID, true); + + wxFileName ff(group); + if (!ff.GetName().empty()) + { + path += ff.GetFullPath(wxPATH_UNIX) + wxCONFIG_PATH_SEPARATOR; + } + + return path; +} + +/* Return value is a key for lookup in a config file */ +RegistryPath PluginManager::SharedKey(const PluginID & ID, const RegistryPath & group, const RegistryPath & key) +{ + auto path = SharedGroup(ID, group); + if (path.empty()) + { + return path; + } + + return path + key; +} + +/* Return value is a key for lookup in a config file */ +RegistryPath PluginManager::PrivateGroup(const PluginID & ID, const RegistryPath & group) +{ + auto path = SettingsPath(ID, false); + + wxFileName ff(group); + if (!ff.GetName().empty()) + { + path += ff.GetFullPath(wxPATH_UNIX) + wxCONFIG_PATH_SEPARATOR; + } + + return path; +} + +/* Return value is a key for lookup in a config file */ +RegistryPath PluginManager::PrivateKey(const PluginID & ID, const RegistryPath & group, const RegistryPath & key) +{ + auto path = PrivateGroup(ID, group); + if (path.empty()) + { + return path; + } + + return path + key; +} + +// Sanitize the ID...not the best solution, but will suffice until this +// is converted to XML. We use base64 encoding to preserve case. +wxString PluginManager::ConvertID(const PluginID & ID) +{ + if (ID.StartsWith(wxT("base64:"))) + { + wxString id = ID.Mid(7); + ArrayOf buf{ id.length() / 4 * 3 }; + id = wxString::FromUTF8(buf.get(), b64decode(id, buf.get())); + return id; + } + + const wxCharBuffer & buf = ID.ToUTF8(); + return wxT("base64:") + b64encode(buf, strlen(buf)); +} + +//////////////////////////////////////////////////////////////////////////////// +// Base64 en/decoding +// +// Original routines marked as public domain and found at: +// +// http://en.wikibooks.org/wiki/Algorithm_implementation/Miscellaneous/Base64 +// +//////////////////////////////////////////////////////////////////////////////// + +// Lookup table for encoding +const static wxChar cset[] = wxT("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"); +const static char padc = wxT('='); + +wxString PluginManager::b64encode(const void *in, int len) +{ + unsigned char *p = (unsigned char *) in; + wxString out; + + unsigned long temp; + for (int i = 0; i < len / 3; i++) + { + temp = (*p++) << 16; //Convert to big endian + temp += (*p++) << 8; + temp += (*p++); + out += cset[(temp & 0x00FC0000) >> 18]; + out += cset[(temp & 0x0003F000) >> 12]; + out += cset[(temp & 0x00000FC0) >> 6]; + out += cset[(temp & 0x0000003F)]; + } + + switch (len % 3) + { + case 1: + temp = (*p++) << 16; //Convert to big endian + out += cset[(temp & 0x00FC0000) >> 18]; + out += cset[(temp & 0x0003F000) >> 12]; + out += padc; + out += padc; + break; + + case 2: + temp = (*p++) << 16; //Convert to big endian + temp += (*p++) << 8; + out += cset[(temp & 0x00FC0000) >> 18]; + out += cset[(temp & 0x0003F000) >> 12]; + out += cset[(temp & 0x00000FC0) >> 6]; + out += padc; + break; + } + + return out; +} + +int PluginManager::b64decode(const wxString &in, void *out) +{ + int len = in.length(); + unsigned char *p = (unsigned char *) out; + + if (len % 4) //Sanity check + { + return 0; + } + + int padding = 0; + if (len) + { + if (in[len - 1] == padc) + { + padding++; + } + + if (in[len - 2] == padc) + { + padding++; + } + } + + //const char *a = in.mb_str(); + //Setup a vector to hold the result + unsigned long temp = 0; //Holds decoded quanta + int i = 0; + while (i < len) + { + for (int quantumPosition = 0; quantumPosition < 4; quantumPosition++) + { + unsigned char c = in[i]; + temp <<= 6; + + if (c >= 0x41 && c <= 0x5A) + { + temp |= c - 0x41; + } + else if (c >= 0x61 && c <= 0x7A) + { + temp |= c - 0x47; + } + else if (c >= 0x30 && c <= 0x39) + { + temp |= c + 0x04; + } + else if (c == 0x2B) + { + temp |= 0x3E; + } + else if (c == 0x2F) + { + temp |= 0x3F; + } + else if (c == padc) + { + switch (len - i) + { + case 1: //One pad character + *p++ = (temp >> 16) & 0x000000FF; + *p++ = (temp >> 8) & 0x000000FF; + return p - (unsigned char *) out; + case 2: //Two pad characters + *p++ = (temp >> 10) & 0x000000FF; + return p - (unsigned char *) out; + } + } + i++; + } + *p++ = (temp >> 16) & 0x000000FF; + *p++ = (temp >> 8) & 0x000000FF; + *p++ = temp & 0x000000FF; + } + + return p - (unsigned char *) out; +} + +// This is defined out-of-line here, to keep ComponentInterface free of other +// #include directives. +TranslatableString ComponentInterface::GetName() +{ + return GetSymbol().Msgid(); +} diff --git a/src/PluginRegistrationDialog.h b/src/PluginRegistrationDialog.h new file mode 100644 index 000000000..d4600e322 --- /dev/null +++ b/src/PluginRegistrationDialog.h @@ -0,0 +1,3139 @@ +/********************************************************************** + + Audacity: A Digital Audio Editor + + PluginManager.cpp + + Leland Lucius + +*******************************************************************//*! + +\file PluginManager.cpp +\brief + +************************************************************************//** +\class PluginManager +\brief PluginManager maintains a list of all plug ins. That covers modules, +effects, generators, analysis-effects, commands. It also has functions +for shared and private configs - which need to move out. +*****************************************************************************/ + + +#include "PluginManager.h" + + + +#include + +#include // for wxUSE_* macros +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "audacity/EffectInterface.h" +#include "audacity/ModuleInterface.h" + +#include "AudacityFileConfig.h" +#include "FileNames.h" +#include "ModuleManager.h" +#include "PlatformCompatibility.h" +#include "Prefs.h" +#include "ShuttleGui.h" +#include "wxFileNameWrapper.h" +#include "widgets/AudacityMessageBox.h" +#include "widgets/ProgressDialog.h" + +#include + +// ============================================================================ +// +// +// +// ============================================================================ +#if wxUSE_ACCESSIBILITY +#include "widgets/WindowAccessible.h" + +class CheckListAx final : public WindowAccessible +{ +public: + CheckListAx(wxListCtrl * window); + + virtual ~ CheckListAx(); + + // Retrieves the address of an IDispatch interface for the specified child. + // All objects must support this property. + wxAccStatus GetChild( int childId, wxAccessible **child ) override; + + // Gets the number of children. + wxAccStatus GetChildCount( int *childCount ) override; + + // Gets the default action for this object (0) or > 0 (the action for a child). + // Return wxACC_OK even if there is no action. actionName is the action, or the empty + // string if there is no action. + // The retrieved string describes the action that is performed on an object, + // not what the object does as a result. For example, a toolbar button that prints + // a document has a default action of "Press" rather than "Prints the current document." + wxAccStatus GetDefaultAction( int childId, wxString *actionName ) override; + + // Returns the description for this object or a child. + wxAccStatus GetDescription( int childId, wxString *description ) override; + + // Gets the window with the keyboard focus. + // If childId is 0 and child is NULL, no object in + // this subhierarchy has the focus. + // If this object has the focus, child should be 'this'. + wxAccStatus GetFocus( int *childId, wxAccessible **child ) override; + + // Returns help text for this object or a child, similar to tooltip text. + wxAccStatus GetHelpText( int childId, wxString *helpText ) override; + + // Returns the keyboard shortcut for this object or child. + // Return e.g. ALT+K + wxAccStatus GetKeyboardShortcut( int childId, wxString *shortcut ) override; + + // Returns the rectangle for this object (id = 0) or a child element (id > 0). + // rect is in screen coordinates. + wxAccStatus GetLocation( wxRect& rect, int elementId ) override; + + // Gets the name of the specified object. + wxAccStatus GetName( int childId, wxString *name ) override; + + // Returns a role constant. + wxAccStatus GetRole( int childId, wxAccRole *role ) override; + + // Gets a variant representing the selected children + // of this object. + // Acceptable values: + // - a null variant (IsNull() returns TRUE) + // - a list variant (GetType() == wxT("list")) + // - an integer representing the selected child element, + // or 0 if this object is selected (GetType() == wxT("long")) + // - a "void*" pointer to a wxAccessible child object + wxAccStatus GetSelections( wxVariant *selections ) override; + + // Returns a state constant. + wxAccStatus GetState( int childId, long* state ) override; + + // Returns a localized string representing the value for the object + // or child. + wxAccStatus GetValue( int childId, wxString *strValue ) override; + + void SetSelected( int item, bool focused = true ); + +private: + wxListCtrl *mParent; + int mLastId; +}; + +CheckListAx::CheckListAx( wxListCtrl * window ) +: WindowAccessible( window ) +{ + mParent = window; + mLastId = -1; +} + +CheckListAx::~CheckListAx() +{ +} + +void CheckListAx::SetSelected( int item, bool focused ) +{ + if (mLastId != -1) + { + NotifyEvent( wxACC_EVENT_OBJECT_SELECTIONREMOVE, + mParent, + wxOBJID_CLIENT, + mLastId ); + mLastId = -1; + } + + if (item != -1) + { + if (focused) + { + NotifyEvent( wxACC_EVENT_OBJECT_FOCUS, + mParent, + wxOBJID_CLIENT, + item + 1 ); + } + + NotifyEvent( wxACC_EVENT_OBJECT_SELECTION, + mParent, + wxOBJID_CLIENT, + item + 1 ); + + mLastId = item + 1; + } +} + +// Retrieves the address of an IDispatch interface for the specified child. +// All objects must support this property. +wxAccStatus CheckListAx::GetChild( int childId, wxAccessible** child ) +{ + if( childId == wxACC_SELF ) + { + *child = this; + } + else + { + *child = NULL; + } + + return wxACC_OK; +} + +// Gets the number of children. +wxAccStatus CheckListAx::GetChildCount( int *childCount ) +{ + *childCount = mParent->GetItemCount(); + + return wxACC_OK; +} + +// Gets the default action for this object (0) or > 0 (the action for a child). +// Return wxACC_OK even if there is no action. actionName is the action, or the empty +// string if there is no action. +// The retrieved string describes the action that is performed on an object, +// not what the object does as a result. For example, a toolbar button that prints +// a document has a default action of "Press" rather than "Prints the current document." +wxAccStatus CheckListAx::GetDefaultAction( int WXUNUSED(childId), wxString *actionName ) +{ + actionName->clear(); + + return wxACC_OK; +} + +// Returns the description for this object or a child. +wxAccStatus CheckListAx::GetDescription( int WXUNUSED(childId), wxString *description ) +{ + description->clear(); + + return wxACC_OK; +} + +// Gets the window with the keyboard focus. +// If childId is 0 and child is NULL, no object in +// this subhierarchy has the focus. +// If this object has the focus, child should be 'this'. +wxAccStatus CheckListAx::GetFocus( int *childId, wxAccessible **child ) +{ + *childId = 0; + *child = this; + + return wxACC_OK; +} + +// Returns help text for this object or a child, similar to tooltip text. +wxAccStatus CheckListAx::GetHelpText( int WXUNUSED(childId), wxString *helpText ) +{ + helpText->clear(); + + return wxACC_OK; +} + +// Returns the keyboard shortcut for this object or child. +// Return e.g. ALT+K +wxAccStatus CheckListAx::GetKeyboardShortcut( int WXUNUSED(childId), wxString *shortcut ) +{ + shortcut->clear(); + + return wxACC_OK; +} + +// Returns the rectangle for this object (id = 0) or a child element (id > 0). +// rect is in screen coordinates. +wxAccStatus CheckListAx::GetLocation( wxRect& rect, int elementId ) +{ + if( elementId == wxACC_SELF ) + { + rect = mParent->GetRect(); + rect.SetPosition( mParent->GetParent()->ClientToScreen( rect.GetPosition() ) ); + } + else + { + if( elementId <= mParent->GetItemCount() ) + { + mParent->GetItemRect( elementId - 1, rect, wxLIST_RECT_LABEL ); + rect.SetPosition( mParent->ClientToScreen( rect.GetPosition() ) ); + } + } + + return wxACC_OK; +} + +// Gets the name of the specified object. +wxAccStatus CheckListAx::GetName( int WXUNUSED(childId), wxString *name ) +{ + *name = mParent->GetName(); + + return wxACC_OK; +} + +// Returns a role constant. +wxAccStatus CheckListAx::GetRole( int childId, wxAccRole *role ) +{ + if( childId == wxACC_SELF ) + { + *role = wxROLE_SYSTEM_LIST; + } + else + { + *role = wxROLE_SYSTEM_LISTITEM; + } + + return wxACC_OK; +} + +// Gets a variant representing the selected children +// of this object. +// Acceptable values: +// - a null variant (IsNull() returns TRUE) +// - a list variant (GetType() == wxT("list")) +// - an integer representing the selected child element, +// or 0 if this object is selected (GetType() == wxT("long")) +// - a "void*" pointer to a wxAccessible child object +wxAccStatus CheckListAx::GetSelections( wxVariant * WXUNUSED(selections) ) +{ + return wxACC_NOT_IMPLEMENTED; +} + +// Returns a state constant. +wxAccStatus CheckListAx::GetState( int childId, long *pState ) +{ + int flag = wxACC_STATE_SYSTEM_FOCUSABLE; + + if( childId == wxACC_SELF ) + { + flag |= wxACC_STATE_SYSTEM_FOCUSED; + } + else + { + wxListItem item; + + item.SetId( childId - 1 ); + item.SetState( wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED ); + item.SetMask( wxLIST_MASK_STATE ); + + if( mParent->GetItem( item ) ) + { + flag |= wxACC_STATE_SYSTEM_SELECTABLE; + + long state = item.GetState(); + + if( state & wxLIST_STATE_FOCUSED ) + { + flag |= wxACC_STATE_SYSTEM_FOCUSED; + } + + if( state & wxLIST_STATE_SELECTED ) + { + flag |= wxACC_STATE_SYSTEM_SELECTED; + } + } + } + + *pState = flag; + + return wxACC_OK; +} + +// Returns a localized string representing the value for the object +// or child. +wxAccStatus CheckListAx::GetValue( int childId, wxString *strValue ) +{ + if( childId == 0 ) + { + return wxACC_OK; + } + else + { + *strValue = mParent->GetItemText( childId - 1 ); + } + + return wxACC_OK; +} + +#endif + +// ============================================================================ +// +// +// +// ============================================================================ + +enum +{ + STATE_Enabled, + STATE_Disabled, + STATE_New, + + STATE_COUNT +}; + +struct ItemData +{ + std::vector plugs; + wxString name; + PluginPath path; + int state; + bool valid; + int nameWidth; + int pathWidth; + int stateWidth; +}; + +using ItemDataMap = std::unordered_map; + +enum +{ + ID_ShowAll = 10000, + ID_ShowEnabled, + ID_ShowDisabled, + ID_ShowNew, + ID_List, + ID_ClearAll, + ID_SelectAll, + ID_Enable, + ID_Disable, +}; + +enum +{ + COL_Name, + COL_State, + COL_Path, + + COL_COUNT +}; + +class PluginRegistrationDialog final : public wxDialogWrapper +{ +public: + // constructors and destructors + PluginRegistrationDialog(wxWindow *parent, EffectType type); + +private: + void Populate(); + void PopulateOrExchange(ShuttleGui & S); + void RegenerateEffectsList(int iShowWhat); + void SetState(int i, bool toggle, bool state = true); + + static int wxCALLBACK SortCompare(wxIntPtr item1, wxIntPtr item2, wxIntPtr sortData); + int SortCompare(ItemData *item1, ItemData *item2); + + void OnChangedVisibility(wxCommandEvent & evt); + void OnSort(wxListEvent & evt); + void DoSort( int col ); + void OnListChar(wxKeyEvent & evt); + void OnOK(wxCommandEvent & evt); + void OnCancel(wxCommandEvent & evt); + void OnSelectAll(wxCommandEvent & evt); + void OnClearAll(wxCommandEvent & evt); + void OnEnable(wxCommandEvent & evt); + void OnDisable(wxCommandEvent & evt); + +private: + EffectType mType; + int mFilter; + + wxArrayString mStates; + ItemDataMap mItems; + + int mSortColumn; + int mSortDirection; + + PluginPath mLongestPath; + + wxListCtrl *mEffects; +#if wxUSE_ACCESSIBILITY + CheckListAx *mAx; +#endif + + DECLARE_EVENT_TABLE() +}; + +BEGIN_EVENT_TABLE(PluginRegistrationDialog, wxDialogWrapper) + EVT_LIST_COL_CLICK(ID_List, PluginRegistrationDialog::OnSort) + EVT_BUTTON(wxID_OK, PluginRegistrationDialog::OnOK) + EVT_BUTTON(wxID_CANCEL, PluginRegistrationDialog::OnCancel) + EVT_BUTTON(ID_ClearAll, PluginRegistrationDialog::OnClearAll) + EVT_BUTTON(ID_SelectAll, PluginRegistrationDialog::OnSelectAll) + EVT_BUTTON(ID_Enable, PluginRegistrationDialog::OnEnable) + EVT_BUTTON(ID_Disable, PluginRegistrationDialog::OnDisable) + EVT_RADIOBUTTON(ID_ShowAll, PluginRegistrationDialog::OnChangedVisibility) + EVT_RADIOBUTTON(ID_ShowEnabled, PluginRegistrationDialog::OnChangedVisibility) + EVT_RADIOBUTTON(ID_ShowDisabled, PluginRegistrationDialog::OnChangedVisibility) + EVT_RADIOBUTTON(ID_ShowNew, PluginRegistrationDialog::OnChangedVisibility) +END_EVENT_TABLE() + +PluginRegistrationDialog::PluginRegistrationDialog(wxWindow *parent, EffectType type) +: wxDialogWrapper(parent, + wxID_ANY, + XO("Manage Plug-ins"), + wxDefaultPosition, wxDefaultSize, + wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) +{ + mType = type; + mEffects = NULL; + SetName(); + + mStates.resize(STATE_COUNT); + mStates[STATE_Enabled] = _("Enabled"); + mStates[STATE_Disabled] = _("Disabled"); + mStates[STATE_New] = _("New"); + + mSortColumn = COL_Name; + mSortDirection = 1; + + Populate(); + + DoSort( mSortColumn ); +} + +void PluginRegistrationDialog::Populate() +{ + //------------------------- Main section -------------------- + ShuttleGui S(this, eIsCreating); + PopulateOrExchange(S); + // ----------------------- End of main section -------------- +} + +/// Defines the dialog and does data exchange with it. +void PluginRegistrationDialog::PopulateOrExchange(ShuttleGui &S) +{ + S.StartVerticalLay(true); + { + /*i18n-hint: The dialog shows a list of plugins with check-boxes + beside each one.*/ +// S.StartStatic(XO("Effects"), true); + S.StartVerticalLay(); + { + S.StartHorizontalLay(wxEXPAND, 0); + { + S.StartHorizontalLay(wxALIGN_LEFT, 0); + { + S.AddPrompt(XXO("Select effects, click the Enable or Disable button, then click OK.")); + } + S.EndHorizontalLay(); + + S.StartHorizontalLay(wxCENTER, 1); + { + S.AddSpace(1); + } + S.EndHorizontalLay(); + + S.StartHorizontalLay(wxALIGN_NOT | wxALIGN_LEFT, 0); + { + wxRadioButton *rb; + + /* i18n-hint: This is before radio buttons selecting which effects to show */ + S.AddPrompt(XXO("Show:")); + rb = S.Id(ID_ShowAll) + /* i18n-hint: Radio button to show all effects */ + .Name(XO("Show all")) + /* i18n-hint: Radio button to show all effects */ + .AddRadioButton(XXO("&All")); +#if wxUSE_ACCESSIBILITY + // so that name can be set on a standard control + rb->SetAccessible(safenew WindowAccessible(rb)); +#endif + + rb = S.Id(ID_ShowDisabled) + /* i18n-hint: Radio button to show just the currently disabled effects */ + .Name(XO("Show disabled")) + /* i18n-hint: Radio button to show just the currently disabled effects */ + .AddRadioButtonToGroup(XXO("D&isabled")); +#if wxUSE_ACCESSIBILITY + // so that name can be set on a standard control + rb->SetAccessible(safenew WindowAccessible(rb)); +#endif + + rb = S.Id(ID_ShowEnabled) + /* i18n-hint: Radio button to show just the currently enabled effects */ + .Name(XO("Show enabled")) + /* i18n-hint: Radio button to show just the currently enabled effects */ + .AddRadioButtonToGroup(XXO("E&nabled")); +#if wxUSE_ACCESSIBILITY + // so that name can be set on a standard control + rb->SetAccessible(safenew WindowAccessible(rb)); +#endif + + rb = S.Id(ID_ShowNew) + /* i18n-hint: Radio button to show just the newly discovered effects */ + .Name(XO("Show new")) + /* i18n-hint: Radio button to show just the newly discovered effects */ + .AddRadioButtonToGroup(XXO("Ne&w")); +#if wxUSE_ACCESSIBILITY + // so that name can be set on a standard control + rb->SetAccessible(safenew WindowAccessible(rb)); +#endif + } + S.EndHorizontalLay(); + } + S.EndHorizontalLay(); + + mEffects = S.Id(ID_List) + .Style(wxSUNKEN_BORDER | wxLC_REPORT | wxLC_HRULES | wxLC_VRULES ) + .ConnectRoot(wxEVT_KEY_DOWN, + &PluginRegistrationDialog::OnListChar) + .AddListControlReportMode({ XO("Name"), XO("State"), XO("Path") }); +#if wxUSE_ACCESSIBILITY + mEffects->SetAccessible(mAx = safenew CheckListAx(mEffects)); +#endif + + S.StartHorizontalLay(wxALIGN_LEFT | wxEXPAND, 0); + { + S.Id(ID_SelectAll).AddButton(XXO("&Select All")); + S.Id(ID_ClearAll).AddButton(XXO("C&lear All")); + + S.StartHorizontalLay(wxALIGN_CENTER); + { + S.AddSpace(1); + } + S.EndHorizontalLay(); + + S.Id(ID_Enable).AddButton(XXO("&Enable")); + S.Id(ID_Disable).AddButton(XXO("&Disable")); + } + S.EndHorizontalLay(); + } +// S.EndStatic(); + S.EndVerticalLay(); + + S.AddStandardButtons(eOkButton | eCancelButton); + } + S.EndVerticalLay(); + + std::vector colWidths; + for (int i = 0, cnt = mEffects->GetColumnCount(); i < cnt; i++) + { + colWidths.push_back(0); + } + + for (int i = 0, cnt = mStates.size(); i < cnt; i++) + { + int x; + mEffects->GetTextExtent(mStates[i], &x, NULL); + colWidths[COL_State] = wxMax(colWidths[COL_State], x + 4); // 2 pixel margin on each side + } + + PluginManager & pm = PluginManager::Get(); + for (auto &plug : pm.AllPlugins()) { + PluginType plugType = plug.GetPluginType(); + if (plugType != PluginTypeEffect && plugType != PluginTypeStub) + continue; + + const auto &path = plug.GetPath(); + ItemData & item = mItems[path]; // will create NEW entry + item.plugs.push_back(&plug); + item.path = path; + item.state = plug.IsEnabled() ? STATE_Enabled : STATE_Disabled; + item.valid = plug.IsValid(); + + if (plugType == PluginTypeEffect) + { + item.name = plug.GetSymbol().Translation(); + } + // This is not right and will not work when other plugin types are added. + // But it's presumed that the plugin manager dialog will be fully developed + // by then. + else if (plugType == PluginTypeStub) + { + wxFileName fname { path }; + item.name = fname.GetName().Trim(false).Trim(true); + if (!item.valid) + { + item.state = STATE_New; + } + } + + int x; + mEffects->GetTextExtent(item.name, &x, NULL); + colWidths[COL_Name] = wxMax(colWidths[COL_Name], x); + + mEffects->GetTextExtent(item.path, &x, NULL); + if (x > colWidths[COL_Path]) + { + mLongestPath = item.path; + } + colWidths[COL_Path] = wxMax(colWidths[COL_Path], x); + } + + wxRect r = wxGetClientDisplayRect(); + + int maxW = 0; + for (int i = 0, cnt = mEffects->GetColumnCount(); i < cnt; i++) + { + int w = colWidths[i] + /* fudge */ 10; + mEffects->SetColumnWidth(i, w); + maxW += w; + } + + // Keep dialog from getting too wide + int w = r.GetWidth() - (GetClientSize().GetWidth() - mEffects->GetSize().GetWidth()); + mEffects->SetMinSize({ std::min(maxW, w), 200 }); + mEffects->SetMaxSize({ w, -1 }); + + RegenerateEffectsList(ID_ShowAll); + + Layout(); + Fit(); + + wxSize sz = GetSize(); + sz.SetWidth(wxMin(sz.GetWidth(), r.GetWidth())); + sz.SetHeight(wxMin(sz.GetHeight(), r.GetHeight())); + SetMinSize(sz); + + // Parent window is usually not there yet, so centre on screen rather than on parent. + CenterOnScreen(); + + if (mEffects->GetItemCount() > 0) + { + // Make sure first item is selected/focused. + mEffects->SetFocus(); + mEffects->SetItemState(0, wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED, wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED); +#if wxUSE_ACCESSIBILITY + mAx->SetSelected(0); +#endif + } + +} + +void PluginRegistrationDialog::RegenerateEffectsList(int filter) +{ + mFilter = filter; + + mEffects->DeleteAllItems(); + + int i = 0; + for (ItemDataMap::iterator iter = mItems.begin(); iter != mItems.end(); ++iter) + { + ItemData & item = iter->second; + bool add = false; + + switch (mFilter) + { + case ID_ShowAll: + add = true; + break; + case ID_ShowNew: + if (item.state == STATE_New) + { + add = true; + } + break; + case ID_ShowEnabled: + if (item.state == STATE_Enabled) + { + add = true; + } + break; + case ID_ShowDisabled: + if (item.state == STATE_Disabled) + { + add = true; + } + break; + } + + if (add) + { + mEffects->InsertItem(i, item.name); + mEffects->SetItem(i, COL_State, mStates[item.state]); + mEffects->SetItem(i, COL_Path, item.path); + mEffects->SetItemPtrData(i, (wxUIntPtr) &item); + + ++i; + } + } + + mEffects->SortItems(SortCompare, (wxUIntPtr) this); + + if (mEffects->GetItemCount() > 0) + { + // Make sure first item is selected/focused. +// mEffects->SetFocus(); + mEffects->SetItemState(0, wxLIST_STATE_FOCUSED|wxLIST_STATE_SELECTED, wxLIST_STATE_FOCUSED|wxLIST_STATE_SELECTED); +#if wxUSE_ACCESSIBILITY + mAx->SetSelected(0, false); +#endif + } +} + +void PluginRegistrationDialog::SetState(int i, bool toggle, bool state) +{ + wxListItem li; + + li.m_mask = wxLIST_MASK_DATA; + li.m_itemId = i; + + mEffects->GetItem(li); + + ItemData *item = (ItemData *) li.m_data; + + // If changing the state of a "New" (stub) entry, then we mark it as valid + // since it will either be registered if "Enabled" or ignored if "Disabled". + if (item->state == STATE_New) + { + item->valid = true; + } + + if (toggle) + { + item->state = item->state == STATE_Enabled ? STATE_Disabled : STATE_Enabled; + } + else + { + item->state = state; + } + + if (mFilter == ID_ShowNew && item->state != STATE_New) + { + mEffects->DeleteItem(i); + } + else if (mFilter == ID_ShowDisabled && item->state != STATE_Disabled) + { + mEffects->DeleteItem(i); + } + else if (mFilter == ID_ShowEnabled && item->state != STATE_Enabled) + { + mEffects->DeleteItem(i); + } + else + { + mEffects->SetItem(i, COL_State, mStates[item->state]); +#if wxUSE_ACCESSIBILITY + mAx->SetSelected(i); +#endif + } +} + +int wxCALLBACK PluginRegistrationDialog::SortCompare(wxIntPtr item1, wxIntPtr item2, wxIntPtr sortData) +{ + PluginRegistrationDialog *dlg = (PluginRegistrationDialog *) sortData; + ItemData *i1 = (ItemData *) item1; + ItemData *i2 = (ItemData *) item2; + + return dlg->SortCompare(i1, i2); +} + +int PluginRegistrationDialog::SortCompare(ItemData *item1, ItemData *item2) +{ + // This function is a three-valued comparator + + wxString *str1; + wxString *str2; + + switch (mSortColumn) + { + case COL_Name: + str1 = &item1->name; + str2 = &item2->name; + break; + case COL_State: + str1 = &mStates[item1->state]; + str2 = &mStates[item2->state]; + break; + case COL_Path: + str1 = &item1->path; + str2 = &item2->path; + break; + default: + return 0; + } + + return str2->CmpNoCase(*str1) * mSortDirection; +} + +void PluginRegistrationDialog::OnChangedVisibility(wxCommandEvent & evt) +{ + // Go and show the relevant items. + RegenerateEffectsList(evt.GetId()); +} + +void PluginRegistrationDialog::OnSort(wxListEvent & evt) +{ + int col = evt.GetColumn(); + DoSort( col ); +} + +void PluginRegistrationDialog::DoSort( int col ) +{ + if (col != mSortColumn) + { + mSortDirection = 1; + } + else + { + mSortDirection *= -1; + } + + mSortColumn = col; + mEffects->SortItems(SortCompare, (wxUIntPtr) this); + + // Without a refresh, wxMac doesn't redisplay the list properly after a sort + mEffects->Refresh(); +} + +void PluginRegistrationDialog::OnListChar(wxKeyEvent & evt) +{ + switch (evt.GetKeyCode()) + { + case WXK_SPACE: + { + int item = mEffects->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_FOCUSED); + if (item != wxNOT_FOUND) + { + SetState(item, true); + } + } + break; + + case WXK_RETURN: + // Don't know why wxListCtrls prevent default dialog action, + // but they do, so handle it. + EmulateButtonClickIfPresent(GetAffirmativeId()); + break; + + default: + evt.Skip(); + break; + } +} + +void PluginRegistrationDialog::OnSelectAll(wxCommandEvent & WXUNUSED(evt)) +{ + for (int i = 0, cnt = mEffects->GetItemCount(); i < cnt; i++) + { + mEffects->SetItemState(i, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); + } +} + +void PluginRegistrationDialog::OnClearAll(wxCommandEvent & WXUNUSED(evt)) +{ + for (int i = 0, cnt = mEffects->GetItemCount(); i < cnt; i++) + { + mEffects->SetItemState(i, 0, wxLIST_STATE_SELECTED); + } +} + +void PluginRegistrationDialog::OnEnable(wxCommandEvent & WXUNUSED(evt)) +{ + std::vector items; + + { + long i = mEffects->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + while (i != wxNOT_FOUND) + { + items.insert(items.begin(), i); + i = mEffects->GetNextItem(i, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + } + } + + for (size_t i = 0, cnt = items.size(); i < cnt; i++) + { + SetState(items[i], false, STATE_Enabled); + } +} + +void PluginRegistrationDialog::OnDisable(wxCommandEvent & WXUNUSED(evt)) +{ + std::vector items; + + { + long i = mEffects->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + while (i != wxNOT_FOUND) + { + items.insert(items.begin(), i); + i = mEffects->GetNextItem(i, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + } + } + + for (size_t i = 0, cnt = items.size(); i < cnt; i++) + { + SetState(items[i], false, STATE_Disabled); + } +} + +void PluginRegistrationDialog::OnOK(wxCommandEvent & WXUNUSED(evt)) +{ + PluginManager & pm = PluginManager::Get(); + ModuleManager & mm = ModuleManager::Get(); + + int enableCount = 0; + for (ItemDataMap::iterator iter = mItems.begin(); iter != mItems.end(); ++iter) + { + ItemData & item = iter->second; + wxString path = item.path; + + if (item.state == STATE_Enabled && item.plugs[0]->GetPluginType() == PluginTypeStub) + { + enableCount++; + } + } + + wxString last3 = mLongestPath + wxT("\n") + + mLongestPath + wxT("\n") + + mLongestPath + wxT("\n"); + + auto msg = XO("Enabling effects or commands:\n\n%s").Format( last3 ); + + // Make sure the progress dialog is deleted before we call EndModal() or + // we will leave the project window in an unusable state on OSX. + // See bug #1192. + { + ProgressDialog progress{ + Verbatim( GetTitle() ), msg, pdlgHideStopButton }; + progress.CenterOnParent(); + + int i = 0; + for (ItemDataMap::iterator iter = mItems.begin(); iter != mItems.end(); ++iter) + { + ItemData & item = iter->second; + wxString path = item.path; + + if (item.state == STATE_Enabled && item.plugs[0]->GetPluginType() == PluginTypeStub) + { + last3 = last3.AfterFirst(wxT('\n')) + item.path + wxT("\n"); + auto status = progress.Update(++i, enableCount, + XO("Enabling effect or command:\n\n%s").Format( last3 )); + if (status == ProgressResult::Cancelled) + { + break; + } + + TranslatableString errMsgs; + + // Try to register the plugin via each provider until one succeeds + for (size_t j = 0, cntj = item.plugs.size(); j < cntj; j++) + { + TranslatableString errMsg; + if (mm.RegisterEffectPlugin(item.plugs[j]->GetProviderID(), path, + errMsg)) + { + for (auto plug : item.plugs) + pm.UnregisterPlugin( + plug->GetProviderID() + wxT("_") + path); + // Bug 1893. We've found a provider that works. + // Error messages from any that failed are no longer useful. + errMsgs = {}; + break; + } + else + { + if (!errMsgs.empty()) + errMsgs.Join( errMsg, '\n' ); + else + errMsgs = errMsg; + } + } + if (!errMsgs.empty()) + AudacityMessageBox( + XO("Effect or Command at %s failed to register:\n%s") + .Format( path, errMsgs ) ); + } + else if (item.state == STATE_New) { + for (auto plug : item.plugs) + plug->SetValid(false); + } + else if (item.state != STATE_New) { + for (auto plug : item.plugs) { + plug->SetEnabled(item.state == STATE_Enabled); + plug->SetValid(item.valid); + } + } + } + + pm.Save(); + } + + EndModal(wxID_OK); +} + +void PluginRegistrationDialog::OnCancel(wxCommandEvent & WXUNUSED(evt)) +{ + EndModal(wxID_CANCEL); +} + + + +/////////////////////////////////////////////////////////////////////////////// +// +// Plugindescriptor +// +/////////////////////////////////////////////////////////////////////////////// + +PluginDescriptor::PluginDescriptor() +{ + mPluginType = PluginTypeNone; + mEnabled = false; + mValid = false; + mInstance = nullptr; + + mEffectType = EffectTypeNone; + mEffectInteractive = false; + mEffectDefault = false; + mEffectLegacy = false; + mEffectRealtime = false; + mEffectAutomatable = false; +} + +PluginDescriptor::~PluginDescriptor() +{ +} + +PluginDescriptor &PluginDescriptor::operator =(PluginDescriptor &&) = default; + +bool PluginDescriptor::IsInstantiated() const +{ + return mInstance != nullptr; +} + +ComponentInterface *PluginDescriptor::GetInstance() +{ + if (!mInstance) + { + if (GetPluginType() == PluginTypeModule) + mInstance = ModuleManager::Get().CreateProviderInstance(GetID(), GetPath()); + else + { + muInstance = ModuleManager::Get().CreateInstance(GetProviderID(), GetPath()); + mInstance = muInstance.get(); + } + } + + return mInstance; +} + +void PluginDescriptor::SetInstance(std::unique_ptr instance) +{ + muInstance = std::move(instance); + mInstance = muInstance.get(); +} + +PluginType PluginDescriptor::GetPluginType() const +{ + return mPluginType; +} + +const PluginID & PluginDescriptor::GetID() const +{ + return mID; +} + +const PluginID & PluginDescriptor::GetProviderID() const +{ + return mProviderID; +} + +const PluginPath & PluginDescriptor::GetPath() const +{ + return mPath; +} + +const ComponentInterfaceSymbol & PluginDescriptor::GetSymbol() const +{ + return mSymbol; +} + +wxString PluginDescriptor::GetUntranslatedVersion() const +{ + return mVersion; +} + +wxString PluginDescriptor::GetVendor() const +{ + return mVendor; +} + +bool PluginDescriptor::IsEnabled() const +{ + return mEnabled; +} + +bool PluginDescriptor::IsValid() const +{ + return mValid; +} + +void PluginDescriptor::SetPluginType(PluginType type) +{ + mPluginType = type; +} + +void PluginDescriptor::SetID(const PluginID & ID) +{ + mID = ID; +} + +void PluginDescriptor::SetProviderID(const PluginID & providerID) +{ + mProviderID = providerID; +} + +void PluginDescriptor::SetPath(const PluginPath & path) +{ + mPath = path; +} + +void PluginDescriptor::SetSymbol(const ComponentInterfaceSymbol & symbol) +{ + mSymbol = symbol; +} + +void PluginDescriptor::SetVersion(const wxString & version) +{ + mVersion = version; +} + +void PluginDescriptor::SetVendor(const wxString & vendor) +{ + mVendor = vendor; +} + +void PluginDescriptor::SetEnabled(bool enable) +{ + mEnabled = enable; +} + +void PluginDescriptor::SetValid(bool valid) +{ + mValid = valid; +} + +// Effects + +wxString PluginDescriptor::GetEffectFamily() const +{ + return mEffectFamily; +} + +EffectType PluginDescriptor::GetEffectType() const +{ + return mEffectType; +} + +bool PluginDescriptor::IsEffectInteractive() const +{ + return mEffectInteractive; +} + +bool PluginDescriptor::IsEffectDefault() const +{ + return mEffectDefault; +} + +bool PluginDescriptor::IsEffectLegacy() const +{ + return mEffectLegacy; +} + +bool PluginDescriptor::IsEffectRealtime() const +{ + return mEffectRealtime; +} + +bool PluginDescriptor::IsEffectAutomatable() const +{ + return mEffectAutomatable; +} + +void PluginDescriptor::SetEffectFamily(const wxString & family) +{ + mEffectFamily = family; +} + +void PluginDescriptor::SetEffectType(EffectType type) +{ + mEffectType = type; +} + +void PluginDescriptor::SetEffectInteractive(bool interactive) +{ + mEffectInteractive = interactive; +} + +void PluginDescriptor::SetEffectDefault(bool dflt) +{ + mEffectDefault = dflt; +} + +void PluginDescriptor::SetEffectLegacy(bool legacy) +{ + mEffectLegacy = legacy; +} + +void PluginDescriptor::SetEffectRealtime(bool realtime) +{ + mEffectRealtime = realtime; +} + +void PluginDescriptor::SetEffectAutomatable(bool automatable) +{ + mEffectAutomatable = automatable; +} + +// Importer + +const wxString & PluginDescriptor::GetImporterIdentifier() const +{ + return mImporterIdentifier; +} + +void PluginDescriptor::SetImporterIdentifier(const wxString & identifier) +{ + mImporterIdentifier = identifier; +} + +const FileExtensions & PluginDescriptor::GetImporterExtensions() + const +{ + return mImporterExtensions; +} + +void PluginDescriptor::SetImporterExtensions( FileExtensions extensions ) +{ + mImporterExtensions = std::move( extensions ); +} + +/////////////////////////////////////////////////////////////////////////////// +// +// PluginManager +// +/////////////////////////////////////////////////////////////////////////////// + +// Registry has the list of plug ins +#define REGVERKEY wxString(wxT("/pluginregistryversion")) +#define REGVERCUR wxString(wxT("1.1")) +#define REGROOT wxString(wxT("/pluginregistry/")) + +// Settings has the values of the plug in settings. +#define SETVERKEY wxString(wxT("/pluginsettingsversion")) +#define SETVERCUR wxString(wxT("1.0")) +#define SETROOT wxString(wxT("/pluginsettings/")) + +#define KEY_ID wxT("ID") +#define KEY_PATH wxT("Path") +#define KEY_SYMBOL wxT("Symbol") +#define KEY_NAME wxT("Name") +#define KEY_VENDOR wxT("Vendor") +#define KEY_VERSION wxT("Version") +#define KEY_DESCRIPTION wxT("Description") +#define KEY_LASTUPDATED wxT("LastUpdated") +#define KEY_ENABLED wxT("Enabled") +#define KEY_VALID wxT("Valid") +#define KEY_PROVIDERID wxT("ProviderID") +#define KEY_EFFECTTYPE wxT("EffectType") +#define KEY_EFFECTFAMILY wxT("EffectFamily") +#define KEY_EFFECTDEFAULT wxT("EffectDefault") +#define KEY_EFFECTINTERACTIVE wxT("EffectInteractive") +#define KEY_EFFECTREALTIME wxT("EffectRealtime") +#define KEY_EFFECTAUTOMATABLE wxT("EffectAutomatable") +#define KEY_EFFECTTYPE_NONE wxT("None") +#define KEY_EFFECTTYPE_ANALYZE wxT("Analyze") +#define KEY_EFFECTTYPE_GENERATE wxT("Generate") +#define KEY_EFFECTTYPE_PROCESS wxT("Process") +#define KEY_EFFECTTYPE_TOOL wxT("Tool") +#define KEY_EFFECTTYPE_HIDDEN wxT("Hidden") +#define KEY_IMPORTERIDENT wxT("ImporterIdent") +//#define KEY_IMPORTERFILTER wxT("ImporterFilter") +#define KEY_IMPORTEREXTENSIONS wxT("ImporterExtensions") + +// ============================================================================ +// +// PluginManagerInterface implementation +// +// ============================================================================ + +const PluginID &PluginManagerInterface::DefaultRegistrationCallback( + ModuleInterface *provider, ComponentInterface *pInterface ) +{ + EffectDefinitionInterface * pEInterface = dynamic_cast(pInterface); + if( pEInterface ) + return PluginManager::Get().RegisterPlugin(provider, pEInterface, PluginTypeEffect); + ComponentInterface * pCInterface = dynamic_cast(pInterface); + if( pCInterface ) + return PluginManager::Get().RegisterPlugin(provider, pCInterface); + static wxString empty; + return empty; +} + +const PluginID &PluginManagerInterface::AudacityCommandRegistrationCallback( + ModuleInterface *provider, ComponentInterface *pInterface ) +{ + ComponentInterface * pCInterface = dynamic_cast(pInterface); + if( pCInterface ) + return PluginManager::Get().RegisterPlugin(provider, pCInterface); + static wxString empty; + return empty; +} + +RegistryPath PluginManager::GetPluginEnabledSetting( const PluginID &ID ) const +{ + auto pPlugin = GetPlugin( ID ); + if ( pPlugin ) + return GetPluginEnabledSetting( *pPlugin ); + return {}; +} + +RegistryPath PluginManager::GetPluginEnabledSetting( + const PluginDescriptor &desc ) const +{ + switch ( desc.GetPluginType() ) { + case PluginTypeModule: { + // Retrieve optional family symbol that was recorded in + // RegisterPlugin() for the module + auto family = desc.GetEffectFamily(); + if ( family.empty() ) // as for built-in effect and command modules + return {}; + else + return wxT('/') + family + wxT("/Enable"); + } + case PluginTypeEffect: + // do NOT use GetEffectFamily() for this descriptor, but instead, + // delegate to the plugin descriptor of the provider, which may + // be different (may be empty) + return GetPluginEnabledSetting( desc.GetProviderID() ); + default: + return {}; + } +} + +bool PluginManager::IsPluginRegistered( + const PluginPath &path, const TranslatableString *pName) +{ + for (auto &pair : mPlugins) { + if (auto &descriptor = pair.second; descriptor.GetPath() == path) { + if (pName) + descriptor.SetSymbol( + { descriptor.GetSymbol().Internal(), *pName }); + return true; + } + } + return false; +} + +const PluginID & PluginManager::RegisterPlugin(ModuleInterface *module) +{ + PluginDescriptor & plug = CreatePlugin(GetID(module), module, PluginTypeModule); + plug.SetEffectFamily(module->GetOptionalFamilySymbol().Internal()); + + plug.SetEnabled(true); + plug.SetValid(true); + + return plug.GetID(); +} + +const PluginID & PluginManager::RegisterPlugin(ModuleInterface *provider, ComponentInterface *command) +{ + PluginDescriptor & plug = CreatePlugin(GetID(command), command, (PluginType)PluginTypeAudacityCommand); + + plug.SetProviderID(PluginManager::GetID(provider)); + + plug.SetEnabled(true); + plug.SetValid(true); + + return plug.GetID(); +} + +const PluginID & PluginManager::RegisterPlugin(ModuleInterface *provider, EffectDefinitionInterface *effect, int type) +{ + PluginDescriptor & plug = CreatePlugin(GetID(effect), effect, (PluginType)type); + + plug.SetProviderID(PluginManager::GetID(provider)); + + plug.SetEffectType(effect->GetClassification()); + plug.SetEffectFamily(effect->GetFamily().Internal()); + plug.SetEffectInteractive(effect->IsInteractive()); + plug.SetEffectDefault(effect->IsDefault()); + plug.SetEffectRealtime(effect->SupportsRealtime()); + plug.SetEffectAutomatable(effect->SupportsAutomation()); + + plug.SetEnabled(true); + plug.SetValid(true); + + return plug.GetID(); +} + +const PluginID & PluginManager::RegisterPlugin(ModuleInterface *provider, ImporterInterface *importer) +{ + PluginDescriptor & plug = CreatePlugin(GetID(importer), importer, PluginTypeImporter); + + plug.SetProviderID(PluginManager::GetID(provider)); + + plug.SetImporterIdentifier(importer->GetPluginStringID()); + plug.SetImporterExtensions(importer->GetSupportedExtensions()); + + return plug.GetID(); +} + +void PluginManager::FindFilesInPathList(const wxString & pattern, + const FilePaths & pathList, + FilePaths & files, + bool directories) +{ + + wxLogNull nolog; + + // Why bother... + if (pattern.empty()) + { + return; + } + + // TODO: We REALLY need to figure out the "Audacity" plug-in path(s) + + FilePaths paths; + + // Add the "per-user" plug-ins directory + { + const wxFileName &ff = FileNames::PlugInDir(); + paths.push_back(ff.GetFullPath()); + } + + // Add the "Audacity" plug-ins directory + wxFileName ff = PlatformCompatibility::GetExecutablePath(); +#if defined(__WXMAC__) + // Path ends for example in "Audacity.app/Contents/MacOSX" + //ff.RemoveLastDir(); + //ff.RemoveLastDir(); + // just remove the MacOSX part. + ff.RemoveLastDir(); +#endif + ff.AppendDir(wxT("plug-ins")); + paths.push_back(ff.GetPath()); + + // Weed out duplicates + for (const auto &filePath : pathList) + { + ff = filePath; + const wxString path{ ff.GetFullPath() }; + if (paths.Index(path, wxFileName::IsCaseSensitive()) == wxNOT_FOUND) + { + paths.push_back(path); + } + } + + // Find all matching files in each path + for (size_t i = 0, cnt = paths.size(); i < cnt; i++) + { + ff = paths[i] + wxFILE_SEP_PATH + pattern; + wxDir::GetAllFiles(ff.GetPath(), &files, ff.GetFullName(), directories ? wxDIR_DEFAULT : wxDIR_FILES); + } + + return; +} + +bool PluginManager::HasSharedConfigGroup(const PluginID & ID, const RegistryPath & group) +{ + return HasGroup(SharedGroup(ID, group)); +} + +bool PluginManager::GetSharedConfigSubgroups(const PluginID & ID, const RegistryPath & group, RegistryPaths & subgroups) +{ + return GetSubgroups(SharedGroup(ID, group), subgroups); +} + +bool PluginManager::GetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, wxString & value, const wxString & defval) +{ + return GetConfig(SharedKey(ID, group, key), value, defval); +} + +bool PluginManager::GetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, int & value, int defval) +{ + return GetConfig(SharedKey(ID, group, key), value, defval); +} + +bool PluginManager::GetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, bool & value, bool defval) +{ + return GetConfig(SharedKey(ID, group, key), value, defval); +} + +bool PluginManager::GetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, float & value, float defval) +{ + return GetConfig(SharedKey(ID, group, key), value, defval); +} + +bool PluginManager::GetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, double & value, double defval) +{ + return GetConfig(SharedKey(ID, group, key), value, defval); +} + +bool PluginManager::SetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const wxString & value) +{ + return SetConfig(SharedKey(ID, group, key), value); +} + +bool PluginManager::SetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const int & value) +{ + return SetConfig(SharedKey(ID, group, key), value); +} + +bool PluginManager::SetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const bool & value) +{ + return SetConfig(SharedKey(ID, group, key), value); +} + +bool PluginManager::SetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const float & value) +{ + return SetConfig(SharedKey(ID, group, key), value); +} + +bool PluginManager::SetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const double & value) +{ + return SetConfig(SharedKey(ID, group, key), value); +} + +bool PluginManager::RemoveSharedConfigSubgroup(const PluginID & ID, const RegistryPath & group) +{ + bool result = GetSettings()->DeleteGroup(SharedGroup(ID, group)); + if (result) + { + GetSettings()->Flush(); + } + + return result; +} + +bool PluginManager::RemoveSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key) +{ + bool result = GetSettings()->DeleteEntry(SharedKey(ID, group, key)); + if (result) + { + GetSettings()->Flush(); + } + + return result; +} + +bool PluginManager::HasPrivateConfigGroup(const PluginID & ID, const RegistryPath & group) +{ + return HasGroup(PrivateGroup(ID, group)); +} + +bool PluginManager::GetPrivateConfigSubgroups(const PluginID & ID, const RegistryPath & group, RegistryPaths & subgroups) +{ + return GetSubgroups(PrivateGroup(ID, group), subgroups); +} + +bool PluginManager::GetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, wxString & value, const wxString & defval) +{ + return GetConfig(PrivateKey(ID, group, key), value, defval); +} + +bool PluginManager::GetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, int & value, int defval) +{ + return GetConfig(PrivateKey(ID, group, key), value, defval); +} + +bool PluginManager::GetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, bool & value, bool defval) +{ + return GetConfig(PrivateKey(ID, group, key), value, defval); +} + +bool PluginManager::GetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, float & value, float defval) +{ + return GetConfig(PrivateKey(ID, group, key), value, defval); +} + +bool PluginManager::GetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, double & value, double defval) +{ + return GetConfig(PrivateKey(ID, group, key), value, defval); +} + +bool PluginManager::SetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const wxString & value) +{ + return SetConfig(PrivateKey(ID, group, key), value); +} + +bool PluginManager::SetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const int & value) +{ + return SetConfig(PrivateKey(ID, group, key), value); +} + +bool PluginManager::SetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const bool & value) +{ + return SetConfig(PrivateKey(ID, group, key), value); +} + +bool PluginManager::SetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const float & value) +{ + return SetConfig(PrivateKey(ID, group, key), value); +} + +bool PluginManager::SetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const double & value) +{ + return SetConfig(PrivateKey(ID, group, key), value); +} + +bool PluginManager::RemovePrivateConfigSubgroup(const PluginID & ID, const RegistryPath & group) +{ + bool result = GetSettings()->DeleteGroup(PrivateGroup(ID, group)); + if (result) + { + GetSettings()->Flush(); + } + + return result; +} + +bool PluginManager::RemovePrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key) +{ + bool result = GetSettings()->DeleteEntry(PrivateKey(ID, group, key)); + if (result) + { + GetSettings()->Flush(); + } + + return result; +} + +// ============================================================================ +// +// PluginManager +// +// ============================================================================ + +// The one and only PluginManager +std::unique_ptr PluginManager::mInstance{}; + +// ---------------------------------------------------------------------------- +// Creation/Destruction +// ---------------------------------------------------------------------------- + +PluginManager::PluginManager() +{ + mSettings = NULL; +} + +PluginManager::~PluginManager() +{ + // Ensure termination (harmless if already done) + Terminate(); +} + +// ---------------------------------------------------------------------------- +// PluginManager implementation +// ---------------------------------------------------------------------------- + +// ============================================================================ +// +// Return reference to singleton +// +// (Thread-safe...no active threading during construction or after destruction) +// ============================================================================ + +PluginManager & PluginManager::Get() +{ + if (!mInstance) + { + mInstance.reset(safenew PluginManager); + } + + return *mInstance; +} + +void PluginManager::Initialize() +{ + // Always load the registry first + Load(); + + // And force load of setting to verify it's accessible + GetSettings(); + + // Then look for providers (they may autoregister plugins) + ModuleManager::Get().DiscoverProviders(); + + // And finally check for updates +#ifndef EXPERIMENTAL_EFFECT_MANAGEMENT + CheckForUpdates(); +#else + const bool kFast = true; + CheckForUpdates( kFast ); +#endif +} + +void PluginManager::Terminate() +{ + // Get rid of all non-module plugins first + PluginMap::iterator iter = mPlugins.begin(); + while (iter != mPlugins.end()) + { + PluginDescriptor & plug = iter->second; + if (plug.GetPluginType() == PluginTypeEffect) + { + mPlugins.erase(iter++); + continue; + } + + ++iter; + } + + // Now get rid of the modules + iter = mPlugins.begin(); + while (iter != mPlugins.end()) + { + mPlugins.erase(iter++); + } +} + +bool PluginManager::DropFile(const wxString &fileName) +{ + auto &mm = ModuleManager::Get(); + const wxFileName src{ fileName }; + + for (auto &plug : PluginsOfType(PluginTypeModule)) { + auto module = static_cast + (mm.CreateProviderInstance(plug.GetID(), plug.GetPath())); + if (! module) + continue; + + const auto &ff = module->InstallPath(); + const auto &extensions = module->GetFileExtensions(); + if ( !ff.empty() && + extensions.Index(src.GetExt(), false) != wxNOT_FOUND ) { + TranslatableString errMsg; + // Do dry-run test of the file format + unsigned nPlugIns = + module->DiscoverPluginsAtPath(fileName, errMsg, {}); + if (nPlugIns) { + // File contents are good for this module, so check no others. + // All branches of this block return true, even in case of + // failure for other reasons, to signal that other drag-and-drop + // actions should not be tried. + + // Find path to copy it + wxFileName dst; + dst.AssignDir( ff ); + dst.SetFullName( src.GetFullName() ); + if ( dst.Exists() ) { + // Query whether to overwrite + bool overwrite = (wxYES == ::AudacityMessageBox( + XO("Overwrite the plug-in file %s?") + .Format( dst.GetFullPath() ), + XO("Plug-in already exists"), + wxYES_NO ) ); + if ( !overwrite ) + return true; + } + + // Move the file or subtree + bool copied = false; + auto dstPath = dst.GetFullPath(); + if ( src.FileExists() ) + // A simple one-file plug-in + copied = FileNames::DoCopyFile( + src.GetFullPath(), dstPath, true ); + else { + // A sub-folder + // such as for some VST packages + // Recursive copy needed -- to do + return true; + } + + if (!copied) { + ::AudacityMessageBox( + XO("Plug-in file is in use. Failed to overwrite") ); + return true; + } + + // Register for real + std::vector ids; + std::vector names; + nPlugIns = module->DiscoverPluginsAtPath(dstPath, errMsg, + [&](ModuleInterface *provider, ComponentInterface *ident) + -> const PluginID& { + // Register as by default, but also collecting the PluginIDs + // and names + auto &id = PluginManagerInterface::DefaultRegistrationCallback( + provider, ident); + ids.push_back(id); + names.push_back( ident->GetSymbol().Translation() ); + return id; + }); + if ( ! nPlugIns ) { + // Unlikely after the dry run succeeded + ::AudacityMessageBox( + XO("Failed to register:\n%s").Format( errMsg ) ); + return true; + } + + // Ask whether to enable the plug-ins + if (auto nIds = ids.size()) { + auto message = XPC( + /* i18n-hint A plug-in is an optional added program for a sound + effect, or generator, or analyzer */ + "Enable this plug-in?\n", + "Enable these plug-ins?\n", + 0, + "plug-ins" + )( nIds ); + for (const auto &name : names) + message.Join( Verbatim( name ), wxT("\n") ); + bool enable = (wxYES == ::AudacityMessageBox( + message, + XO("Enable new plug-ins"), + wxYES_NO ) ); + for (const auto &id : ids) + mPlugins[id].SetEnabled(enable); + // Make changes to enabled status persist: + this->Save(); + } + + return true; + } + } + } + + return false; +} + +void PluginManager::Load() +{ + // Create/Open the registry + auto pRegistry = AudacityFileConfig::Create( + {}, {}, FileNames::PluginRegistry()); + auto ®istry = *pRegistry; + + // If this group doesn't exist then we have something that's not a registry. + // We should probably warn the user, but it's pretty unlikely that this will happen. + if (!registry.HasGroup(REGROOT)) + { + // Must start over + registry.DeleteAll(); + registry.Flush(); + return; + } + + // Check for a registry version that we can understand + // TODO: Should also check for a registry file that is newer than + // what we can understand. + wxString regver = registry.Read(REGVERKEY); + if (regver < REGVERCUR ) + { + // Conversion code here, for when registry version changes. + + // We iterate through the effects, possibly updating their info. + wxString groupName; + long groupIndex; + wxString group = GetPluginTypeString(PluginTypeEffect); + wxString cfgPath = REGROOT + group + wxCONFIG_PATH_SEPARATOR; + wxArrayString groupsToDelete; + + registry.SetPath(cfgPath); + for (bool cont = registry.GetFirstGroup(groupName, groupIndex); + cont; + registry.SetPath(cfgPath), + cont = registry.GetNextGroup(groupName, groupIndex)) + { + registry.SetPath(groupName); + wxString effectSymbol = registry.Read(KEY_SYMBOL, ""); + wxString effectVersion = registry.Read(KEY_VERSION, ""); + + + // For 2.3.0 the plugins we distribute have moved around. + // So we upped the registry version number to 1.1. + // These particular config edits were originally written to fix Bug 1914. + if (regver <= "1.0") { + // Nyquist prompt is a built-in that has moved to the tools menu. + if (effectSymbol == NYQUIST_PROMPT_ID) { + registry.Write(KEY_EFFECTTYPE, "Tool"); + // Old version of SDE was in Analyze menu. Now it is in Tools. + // We don't want both the old and the new. + } else if ((effectSymbol == "Sample Data Export") && (effectVersion == "n/a")) { + groupsToDelete.push_back(cfgPath + groupName); + // Old version of SDI was in Generate menu. Now it is in Tools. + } else if ((effectSymbol == "Sample Data Import") && (effectVersion == "n/a")) { + groupsToDelete.push_back(cfgPath + groupName); + } + } + + } + // Doing the deletion within the search loop risked skipping some items, + // hence the delayed delete. + for (unsigned int i = 0; i < groupsToDelete.size(); i++) { + registry.DeleteGroup(groupsToDelete[i]); + } + registry.SetPath(""); + registry.Write(REGVERKEY, REGVERCUR); + // Updates done. Make sure we read the updated data later. + registry.Flush(); + } + + // Load all provider plugins first + LoadGroup(®istry, PluginTypeModule); + + // Now the rest + LoadGroup(®istry, PluginTypeEffect); + LoadGroup(®istry, PluginTypeAudacityCommand ); + LoadGroup(®istry, PluginTypeExporter); + LoadGroup(®istry, PluginTypeImporter); + + LoadGroup(®istry, PluginTypeStub); + return; +} + +void PluginManager::LoadGroup(FileConfig *pRegistry, PluginType type) +{ +#ifdef __WXMAC__ + // Bug 1590: On Mac, we should purge the registry of Nyquist plug-ins + // bundled with other versions of Audacity, assuming both versions + // were properly installed in /Applications (or whatever it is called in + // your locale) + + const auto fullExePath = PlatformCompatibility::GetExecutablePath(); + + // Strip rightmost path components up to *.app + wxFileName exeFn{ fullExePath }; + exeFn.SetEmptyExt(); + exeFn.SetName(wxString{}); + while(exeFn.GetDirCount() && !exeFn.GetDirs().back().EndsWith(".app")) + exeFn.RemoveLastDir(); + + const auto goodPath = exeFn.GetPath(); + + if(exeFn.GetDirCount()) + exeFn.RemoveLastDir(); + const auto possiblyBadPath = exeFn.GetPath(); + + auto AcceptPath = [&](const wxString &path) { + if (!path.StartsWith(possiblyBadPath)) + // Assume it's not under /Applications + return true; + if (path.StartsWith(goodPath)) + // It's bundled with this executable + return true; + return false; + }; +#else + auto AcceptPath = [](const wxString&){ return true; }; +#endif + + wxString strVal; + bool boolVal; + wxString groupName; + long groupIndex; + wxString group = GetPluginTypeString(type); + wxString cfgPath = REGROOT + group + wxCONFIG_PATH_SEPARATOR; + + pRegistry->SetPath(cfgPath); + for (bool cont = pRegistry->GetFirstGroup(groupName, groupIndex); + cont; + pRegistry->SetPath(cfgPath), + cont = pRegistry->GetNextGroup(groupName, groupIndex)) + { + PluginDescriptor plug; + + pRegistry->SetPath(groupName); + + groupName = ConvertID(groupName); + + // Bypass group if the ID is already in use + if (mPlugins.count(groupName)) + continue; + + // Set the ID and type + plug.SetID(groupName); + plug.SetPluginType(type); + + // Get the provider ID and bypass group if not found + if (!pRegistry->Read(KEY_PROVIDERID, &strVal, wxEmptyString)) + { + // Bypass group if the provider isn't valid + if (!strVal.empty() && !mPlugins.count(strVal)) + continue; + } + plug.SetProviderID(PluginID(strVal)); + + // Get the path (optional) + pRegistry->Read(KEY_PATH, &strVal, wxEmptyString); + if (!AcceptPath(strVal)) + // Ignore the obsolete path in the config file, during session, + // but don't remove it from the file. Maybe you really want to + // switch back to the other version of Audacity and lose nothing. + continue; + plug.SetPath(strVal); + + /* + // PRL: Ignore names written in configs before 2.3.0! + // use Internal string only! Let the present version of Audacity map + // that to a user-visible string. + // Get the name and bypass group if not found + if (!pRegistry->Read(KEY_NAME, &strVal)) + { + continue; + } + plug.SetName(strVal); + */ + + // Get the symbol...Audacity 2.3.0 or later requires it + // bypass group if not found + // Note, KEY_SYMBOL started getting written to config files in 2.1.0. + // KEY_NAME (now ignored) was written before that, but only for VST + // effects. + if (!pRegistry->Read(KEY_SYMBOL, &strVal)) + continue; + + // Related to Bug2778: config file only remembered an internal name, + // so this symbol may not contain the correct TranslatableString. + // See calls to IsPluginRegistered which can correct that. + plug.SetSymbol(strVal); + + // Get the version and bypass group if not found + if (!pRegistry->Read(KEY_VERSION, &strVal)) + { + continue; + } + plug.SetVersion(strVal); + + // Get the vendor and bypass group if not found + if (!pRegistry->Read(KEY_VENDOR, &strVal)) + { + continue; + } + plug.SetVendor( strVal ); + +#if 0 + // This was done before version 2.2.2, but the value was not really used + // But absence of a value will cause early versions to skip the group + // Therefore we still write a blank to keep pluginregistry.cfg + // backwards-compatible + + // Get the description and bypass group if not found + if (!pRegistry->Read(KEY_DESCRIPTION, &strVal)) + { + continue; + } +#endif + + // Is it enabled...default to no if not found + pRegistry->Read(KEY_ENABLED, &boolVal, false); + plug.SetEnabled(boolVal); + + // Is it valid...default to no if not found + pRegistry->Read(KEY_VALID, &boolVal, false); + plug.SetValid(boolVal); + + switch (type) + { + case PluginTypeModule: + { + // Nothing to do here yet + } + break; + + case PluginTypeEffect: + { + // Get the effect type and bypass group if not found + if (!pRegistry->Read(KEY_EFFECTTYPE, &strVal)) + continue; + + if (strVal == KEY_EFFECTTYPE_NONE) + plug.SetEffectType(EffectTypeNone); + else if (strVal == KEY_EFFECTTYPE_ANALYZE) + plug.SetEffectType(EffectTypeAnalyze); + else if (strVal == KEY_EFFECTTYPE_GENERATE) + plug.SetEffectType(EffectTypeGenerate); + else if (strVal == KEY_EFFECTTYPE_PROCESS) + plug.SetEffectType(EffectTypeProcess); + else if (strVal == KEY_EFFECTTYPE_TOOL) + plug.SetEffectType(EffectTypeTool); + else if (strVal == KEY_EFFECTTYPE_HIDDEN) + plug.SetEffectType(EffectTypeHidden); + else + continue; + + // Get the effect family and bypass group if not found + if (!pRegistry->Read(KEY_EFFECTFAMILY, &strVal)) + { + continue; + } + plug.SetEffectFamily(strVal); + + // Is it a default (above the line) effect and bypass group if not found + if (!pRegistry->Read(KEY_EFFECTDEFAULT, &boolVal)) + { + continue; + } + plug.SetEffectDefault(boolVal); + + // Is it an interactive effect and bypass group if not found + if (!pRegistry->Read(KEY_EFFECTINTERACTIVE, &boolVal)) + { + continue; + } + plug.SetEffectInteractive(boolVal); + + // Is it a realtime capable effect and bypass group if not found + if (!pRegistry->Read(KEY_EFFECTREALTIME, &boolVal)) + { + continue; + } + plug.SetEffectRealtime(boolVal); + + // Does the effect support automation...bypass group if not found + if (!pRegistry->Read(KEY_EFFECTAUTOMATABLE, &boolVal)) + { + continue; + } + plug.SetEffectAutomatable(boolVal); + } + break; + + case PluginTypeImporter: + { + // Get the importer identifier and bypass group if not found + if (!pRegistry->Read(KEY_IMPORTERIDENT, &strVal)) + { + continue; + } + plug.SetImporterIdentifier(strVal); + + // Get the importer extensions and bypass group if not found + if (!pRegistry->Read(KEY_IMPORTEREXTENSIONS, &strVal)) + { + continue; + } + FileExtensions extensions; + wxStringTokenizer tkr(strVal, wxT(":")); + while (tkr.HasMoreTokens()) + { + extensions.push_back(tkr.GetNextToken()); + } + plug.SetImporterExtensions(extensions); + } + break; + + case PluginTypeStub: + { + // Nothing additional for stubs + } + break; + + // Not used by 2.1.1 or greater and should be removed after a few releases past 2.1.0. + case PluginTypeNone: + { + // Used for stub groups + } + break; + + default: + { + continue; + } + } + + // Everything checked out...accept the plugin + mPlugins[groupName] = std::move(plug); + } + + return; +} + +void PluginManager::Save() +{ + // Create/Open the registry + auto pRegistry = AudacityFileConfig::Create( + {}, {}, FileNames::PluginRegistry()); + auto ®istry = *pRegistry; + + // Clear it out + registry.DeleteAll(); + + // Write the version string + registry.Write(REGVERKEY, REGVERCUR); + + // Save the individual groups + SaveGroup(®istry, PluginTypeEffect); + SaveGroup(®istry, PluginTypeExporter); + SaveGroup(®istry, PluginTypeAudacityCommand); + SaveGroup(®istry, PluginTypeImporter); + SaveGroup(®istry, PluginTypeStub); + + // Not used by 2.1.1 or greater, but must save to allow users to switch between 2.1.0 + // and 2.1.1+. This should be removed after a few releases past 2.1.0. + //SaveGroup(®istry, PluginTypeNone); + + // And now the providers + SaveGroup(®istry, PluginTypeModule); + + // Just to be safe + registry.Flush(); +} + +void PluginManager::SaveGroup(FileConfig *pRegistry, PluginType type) +{ + wxString group = GetPluginTypeString(type); + for (auto &pair : mPlugins) { + auto & plug = pair.second; + + if (plug.GetPluginType() != type) + { + continue; + } + + pRegistry->SetPath(REGROOT + group + wxCONFIG_PATH_SEPARATOR + ConvertID(plug.GetID())); + + pRegistry->Write(KEY_PATH, plug.GetPath()); + + // See comments with the corresponding load-time call to SetSymbol(). + pRegistry->Write(KEY_SYMBOL, plug.GetSymbol().Internal()); + + // PRL: Writing KEY_NAME which is no longer read, but older Audacity + // versions expect to find it. + pRegistry->Write(KEY_NAME, plug.GetSymbol().Msgid().MSGID()); + + pRegistry->Write(KEY_VERSION, plug.GetUntranslatedVersion()); + pRegistry->Write(KEY_VENDOR, plug.GetVendor()); + // Write a blank -- see comments in LoadGroup: + pRegistry->Write(KEY_DESCRIPTION, wxString{}); + pRegistry->Write(KEY_PROVIDERID, plug.GetProviderID()); + pRegistry->Write(KEY_ENABLED, plug.IsEnabled()); + pRegistry->Write(KEY_VALID, plug.IsValid()); + + switch (type) + { + case PluginTypeModule: + break; + + case PluginTypeEffect: + { + EffectType etype = plug.GetEffectType(); + wxString stype; + if (etype == EffectTypeNone) + stype = KEY_EFFECTTYPE_NONE; + else if (etype == EffectTypeAnalyze) + stype = KEY_EFFECTTYPE_ANALYZE; + else if (etype == EffectTypeGenerate) + stype = KEY_EFFECTTYPE_GENERATE; + else if (etype == EffectTypeProcess) + stype = KEY_EFFECTTYPE_PROCESS; + else if (etype == EffectTypeTool) + stype = KEY_EFFECTTYPE_TOOL; + else if (etype == EffectTypeHidden) + stype = KEY_EFFECTTYPE_HIDDEN; + + pRegistry->Write(KEY_EFFECTTYPE, stype); + pRegistry->Write(KEY_EFFECTFAMILY, plug.GetEffectFamily()); + pRegistry->Write(KEY_EFFECTDEFAULT, plug.IsEffectDefault()); + pRegistry->Write(KEY_EFFECTINTERACTIVE, plug.IsEffectInteractive()); + pRegistry->Write(KEY_EFFECTREALTIME, plug.IsEffectRealtime()); + pRegistry->Write(KEY_EFFECTAUTOMATABLE, plug.IsEffectAutomatable()); + } + break; + + case PluginTypeImporter: + { + pRegistry->Write(KEY_IMPORTERIDENT, plug.GetImporterIdentifier()); + const auto & extensions = plug.GetImporterExtensions(); + wxString strExt; + for (size_t i = 0, cnt = extensions.size(); i < cnt; i++) + { + strExt += extensions[i] + wxT(":"); + } + strExt.RemoveLast(1); + pRegistry->Write(KEY_IMPORTEREXTENSIONS, strExt); + } + break; + + default: + break; + } + } + + return; +} + +// If bFast is true, do not do a full check. Just check the ones +// that are quick to check. Currently (Feb 2017) just Nyquist +// and built-ins. +void PluginManager::CheckForUpdates(bool bFast) +{ + ModuleManager & mm = ModuleManager::Get(); + wxArrayString pathIndex; + for (auto &pair : mPlugins) { + auto &plug = pair.second; + + // Bypass 2.1.0 placeholders...remove this after a few releases past 2.1.0 + if (plug.GetPluginType() != PluginTypeNone) + pathIndex.push_back(plug.GetPath().BeforeFirst(wxT(';'))); + } + + // Check all known plugins to ensure they are still valid and scan for NEW ones. + // + // All NEW plugins get a stub entry created that will remain in place until the + // user enables or disables the plugin. + // + // Because we use the plugins "path" as returned by the providers, we can actually + // have multiple providers report the same path since, at this point, they only + // know that the path might possibly be one supported by the provider. + // + // When the user enables the plugin, each provider that reported it will be asked + // to register the plugin. + for (auto &pair : mPlugins) { + auto &plug = pair.second; + const PluginID & plugID = plug.GetID(); + const wxString & plugPath = plug.GetPath(); + PluginType plugType = plug.GetPluginType(); + + // Bypass 2.1.0 placeholders...remove this after a few releases past 2.1.0 + if (plugType == PluginTypeNone) + { + continue; + } + + if ( plugType == PluginTypeModule ) + { + if( bFast ) + { + // Skip modules, when doing a fast refresh/check. + } + else if (!mm.IsProviderValid(plugID, plugPath)) + { + plug.SetEnabled(false); + plug.SetValid(false); + } + else + { + // Collect plugin paths + auto paths = mm.FindPluginsForProvider(plugID, plugPath); + for (size_t i = 0, cnt = paths.size(); i < cnt; i++) + { + wxString path = paths[i].BeforeFirst(wxT(';'));; + if ( ! make_iterator_range( pathIndex ).contains( path ) ) + { + PluginID ID = plugID + wxT("_") + path; + PluginDescriptor & plug2 = mPlugins[ID]; // This will create a NEW descriptor + plug2.SetPluginType(PluginTypeStub); + plug2.SetID(ID); + plug2.SetProviderID(plugID); + plug2.SetPath(path); + plug2.SetEnabled(false); + plug2.SetValid(false); + } + } + } + } + else if (plugType != PluginTypeNone && plugType != PluginTypeStub) + { + plug.SetValid(mm.IsPluginValid(plug.GetProviderID(), plugPath, bFast)); + if (!plug.IsValid()) + { + plug.SetEnabled(false); + } + } + } + + Save(); + + return; +} + +bool PluginManager::ShowManager(wxWindow *parent, EffectType type) +{ + CheckForUpdates(); + + PluginRegistrationDialog dlg(parent, type); + return dlg.ShowModal() == wxID_OK; +} + +// Here solely for the purpose of Nyquist Workbench until +// a better solution is devised. +const PluginID & PluginManager::RegisterPlugin( + std::unique_ptr effect, PluginType type) +{ + PluginDescriptor & plug = + CreatePlugin(GetID(effect.get()), effect.get(), type); + + plug.SetEffectType(effect->GetType()); + plug.SetEffectFamily(effect->GetFamily().Internal()); + plug.SetEffectInteractive(effect->IsInteractive()); + plug.SetEffectDefault(effect->IsDefault()); + plug.SetEffectRealtime(effect->SupportsRealtime()); + plug.SetEffectAutomatable(effect->SupportsAutomation()); + + plug.SetInstance(std::move(effect)); + plug.SetEffectLegacy(true); + plug.SetEnabled(true); + plug.SetValid(true); + + return plug.GetID(); +} + +void PluginManager::UnregisterPlugin(const PluginID & ID) +{ + mPlugins.erase(ID); +} + +int PluginManager::GetPluginCount(PluginType type) +{ + return count_if(mPlugins.begin(), mPlugins.end(), [type](auto &pair){ + return pair.second.GetPluginType() == type; }); +} + +const PluginDescriptor *PluginManager::GetPlugin(const PluginID & ID) const +{ + if (auto iter = mPlugins.find(ID); iter == mPlugins.end()) + return nullptr; + else + return &iter->second; +} + +void PluginManager::Iterator::Advance(bool incrementing) +{ + const auto end = mPm.mPlugins.end(); + if (incrementing && mIterator != end) + ++mIterator; + bool all = mPluginType == PluginTypeNone && mEffectType == EffectTypeNone; + for (; mIterator != end; ++mIterator) { + auto &plug = mIterator->second; + if (!all && !(plug.IsValid() && plug.IsEnabled())) + continue; + auto plugType = plug.GetPluginType(); + if ((mPluginType == PluginTypeNone || (plugType & mPluginType)) && + (mEffectType == EffectTypeNone || plug.GetEffectType() == mEffectType)) { + if (!all && (plugType & PluginTypeEffect)) { + // This preference may be written by EffectsPrefs + auto setting = mPm.GetPluginEnabledSetting( plug ); + if (!(setting.empty() || gPrefs->Read( setting, true ))) + continue; + } + // Pause iteration at this match + break; + } + } +} + +PluginManager::Iterator::Iterator(PluginManager &manager) +: mPm{ manager } +, mIterator{ manager.mPlugins.begin() } +{ +} + +PluginManager::Iterator::Iterator(PluginManager &manager, int type) +: mPm{ manager } +, mIterator{ manager.mPlugins.begin() } +, mPluginType{ type } +{ + Advance(false); +} + +PluginManager::Iterator::Iterator(PluginManager &manager, EffectType type) +: mPm{ manager } +, mIterator{ manager.mPlugins.begin() } +, mEffectType{ type } +{ + Advance(false); +} + +auto PluginManager::Iterator::operator ++() -> Iterator & +{ + Advance(true); + return *this; +} + +bool PluginManager::IsPluginEnabled(const PluginID & ID) +{ + if (auto iter = mPlugins.find(ID); iter == mPlugins.end()) + return false; + else + return iter->second.IsEnabled(); +} + +void PluginManager::EnablePlugin(const PluginID & ID, bool enable) +{ + if (auto iter = mPlugins.find(ID); iter == mPlugins.end()) + return; + else + iter->second.SetEnabled(enable); +} + +const ComponentInterfaceSymbol & PluginManager::GetSymbol(const PluginID & ID) +{ + if (auto iter = mPlugins.find(ID); iter == mPlugins.end()) { + static ComponentInterfaceSymbol empty; + return empty; + } + else + return iter->second.GetSymbol(); +} + +ComponentInterface *PluginManager::GetInstance(const PluginID & ID) +{ + if (auto iter = mPlugins.find(ID); iter == mPlugins.end()) + return nullptr; + else { + auto &plug = iter->second; + + // If not dealing with legacy effects, make sure the provider is loaded + if (!plug.IsEffectLegacy()) + { + const PluginID & prov = plug.GetProviderID(); + if (auto iter2 = mPlugins.find(prov); iter2 == mPlugins.end()) + return nullptr; + else + iter2->second.GetInstance(); + } + + return plug.GetInstance(); + } +} + +PluginID PluginManager::GetID(ModuleInterface *module) +{ + return wxString::Format(wxT("%s_%s_%s_%s_%s"), + GetPluginTypeString(PluginTypeModule), + wxEmptyString, + module->GetVendor().Internal(), + module->GetSymbol().Internal(), + module->GetPath()); +} + +PluginID PluginManager::GetID(ComponentInterface *command) +{ + return wxString::Format(wxT("%s_%s_%s_%s_%s"), + GetPluginTypeString(PluginTypeAudacityCommand), + wxEmptyString, + command->GetVendor().Internal(), + command->GetSymbol().Internal(), + command->GetPath()); +} + +PluginID PluginManager::GetID(EffectDefinitionInterface *effect) +{ + return wxString::Format(wxT("%s_%s_%s_%s_%s"), + GetPluginTypeString(PluginTypeEffect), + effect->GetFamily().Internal(), + effect->GetVendor().Internal(), + effect->GetSymbol().Internal(), + effect->GetPath()); +} + +PluginID PluginManager::GetID(ImporterInterface *importer) +{ + return wxString::Format(wxT("%s_%s_%s_%s_%s"), + GetPluginTypeString(PluginTypeImporter), + wxEmptyString, + importer->GetVendor().Internal(), + importer->GetSymbol().Internal(), + importer->GetPath()); +} + +// This string persists in configuration files +// So config compatibility will break if it is changed across Audacity versions +wxString PluginManager::GetPluginTypeString(PluginType type) +{ + wxString str; + + switch (type) + { + default: + case PluginTypeNone: + str = wxT("Placeholder"); + break; + case PluginTypeStub: + str = wxT("Stub"); + break; + case PluginTypeEffect: + str = wxT("Effect"); + break; + case PluginTypeAudacityCommand: + str = wxT("Generic"); + break; + case PluginTypeExporter: + str = wxT("Exporter"); + break; + case PluginTypeImporter: + str = wxT("Importer"); + break; + case PluginTypeModule: + str = wxT("Module"); + break; + } + + return str; +} + +PluginDescriptor & PluginManager::CreatePlugin(const PluginID & id, + ComponentInterface *ident, + PluginType type) +{ + // This will either create a NEW entry or replace an existing entry + PluginDescriptor & plug = mPlugins[id]; + + plug.SetPluginType(type); + + plug.SetID(id); + plug.SetPath(ident->GetPath()); + plug.SetSymbol(ident->GetSymbol()); + plug.SetVendor(ident->GetVendor().Internal()); + plug.SetVersion(ident->GetVersion()); + + return plug; +} + +FileConfig *PluginManager::GetSettings() +{ + if (!mSettings) + { + mSettings = + AudacityFileConfig::Create({}, {}, FileNames::PluginSettings()); + + // Check for a settings version that we can understand + if (mSettings->HasEntry(SETVERKEY)) + { + wxString setver = mSettings->Read(SETVERKEY, SETVERKEY); + if (setver < SETVERCUR ) + { + // This is where we'd put in conversion code when the + // settings version changes. + // + // Should also check for a settings file that is newer than + // what we can understand. + } + } + else + { + // Make sure is has a version string + mSettings->Write(SETVERKEY, SETVERCUR); + mSettings->Flush(); + } + } + + return mSettings.get(); +} + +bool PluginManager::HasGroup(const RegistryPath & group) +{ + auto settings = GetSettings(); + + bool res = settings->HasGroup(group); + if (res) + { + // The group exists, but empty groups aren't considered valid + wxString oldPath = settings->GetPath(); + settings->SetPath(group); + res = settings->GetNumberOfEntries() || settings->GetNumberOfGroups(); + settings->SetPath(oldPath); + } + + return res; +} + +bool PluginManager::GetSubgroups(const RegistryPath & group, RegistryPaths & subgroups) +{ + if (group.empty() || !HasGroup(group)) + { + return false; + } + + wxString path = GetSettings()->GetPath(); + GetSettings()->SetPath(group); + + wxString name; + long index = 0; + if (GetSettings()->GetFirstGroup(name, index)) + { + do + { + subgroups.push_back(name); + } while (GetSettings()->GetNextGroup(name, index)); + } + + GetSettings()->SetPath(path); + + return true; +} + +bool PluginManager::GetConfig(const RegistryPath & key, int & value, int defval) +{ + bool result = false; + + if (!key.empty()) + { + result = GetSettings()->Read(key, &value, defval); + } + + return result; +} + +bool PluginManager::GetConfig(const RegistryPath & key, wxString & value, const wxString & defval) +{ + bool result = false; + + if (!key.empty()) + { + wxString wxval; + + result = GetSettings()->Read(key, &wxval, defval); + + value = wxval; + } + + return result; +} + +bool PluginManager::GetConfig(const RegistryPath & key, bool & value, bool defval) +{ + bool result = false; + + if (!key.empty()) + { + result = GetSettings()->Read(key, &value, defval); + } + + return result; +} + +bool PluginManager::GetConfig(const RegistryPath & key, float & value, float defval) +{ + bool result = false; + + if (!key.empty()) + { + double dval = 0.0; + + result = GetSettings()->Read(key, &dval, (double) defval); + + value = (float) dval; + } + + return result; +} + +bool PluginManager::GetConfig(const RegistryPath & key, double & value, double defval) +{ + bool result = false; + + if (!key.empty()) + { + result = GetSettings()->Read(key, &value, defval); + } + + return result; +} + +bool PluginManager::SetConfig(const RegistryPath & key, const wxString & value) +{ + bool result = false; + + if (!key.empty()) + { + wxString wxval = value; + result = GetSettings()->Write(key, wxval); + if (result) + { + result = GetSettings()->Flush(); + } + } + + return result; +} + +bool PluginManager::SetConfig(const RegistryPath & key, const int & value) +{ + bool result = false; + + if (!key.empty()) + { + result = GetSettings()->Write(key, value); + if (result) + { + result = GetSettings()->Flush(); + } + } + + return result; +} + +bool PluginManager::SetConfig(const RegistryPath & key, const bool & value) +{ + bool result = false; + + if (!key.empty()) + { + result = GetSettings()->Write(key, value); + if (result) + { + result = GetSettings()->Flush(); + } + } + + return result; +} + +bool PluginManager::SetConfig(const RegistryPath & key, const float & value) +{ + bool result = false; + + if (!key.empty()) + { + result = GetSettings()->Write(key, value); + if (result) + { + result = GetSettings()->Flush(); + } + } + + return result; +} + +bool PluginManager::SetConfig(const RegistryPath & key, const double & value) +{ + bool result = false; + + if (!key.empty()) + { + result = GetSettings()->Write(key, value); + if (result) + { + result = GetSettings()->Flush(); + } + } + + return result; +} + +/* Return value is a key for lookup in a config file */ +RegistryPath PluginManager::SettingsPath(const PluginID & ID, bool shared) +{ + // All the strings reported by PluginDescriptor and used in this function + // persist in the plugin settings configuration file, so they should not + // be changed across Audacity versions, or else compatibility of the + // configuration files will break. + + if (auto iter = mPlugins.find(ID); iter == mPlugins.end()) + return {}; + else { + const PluginDescriptor & plug = iter->second; + + wxString id = GetPluginTypeString(plug.GetPluginType()) + + wxT("_") + + plug.GetEffectFamily() + // is empty for non-Effects + wxT("_") + + plug.GetVendor() + + wxT("_") + + (shared ? wxString{} : plug.GetSymbol().Internal()); + + return SETROOT + + ConvertID(id) + + wxCONFIG_PATH_SEPARATOR + + (shared ? wxT("shared") : wxT("private")) + + wxCONFIG_PATH_SEPARATOR; + } +} + +/* Return value is a key for lookup in a config file */ +RegistryPath PluginManager::SharedGroup(const PluginID & ID, const RegistryPath & group) +{ + wxString path = SettingsPath(ID, true); + + wxFileName ff(group); + if (!ff.GetName().empty()) + { + path += ff.GetFullPath(wxPATH_UNIX) + wxCONFIG_PATH_SEPARATOR; + } + + return path; +} + +/* Return value is a key for lookup in a config file */ +RegistryPath PluginManager::SharedKey(const PluginID & ID, const RegistryPath & group, const RegistryPath & key) +{ + auto path = SharedGroup(ID, group); + if (path.empty()) + { + return path; + } + + return path + key; +} + +/* Return value is a key for lookup in a config file */ +RegistryPath PluginManager::PrivateGroup(const PluginID & ID, const RegistryPath & group) +{ + auto path = SettingsPath(ID, false); + + wxFileName ff(group); + if (!ff.GetName().empty()) + { + path += ff.GetFullPath(wxPATH_UNIX) + wxCONFIG_PATH_SEPARATOR; + } + + return path; +} + +/* Return value is a key for lookup in a config file */ +RegistryPath PluginManager::PrivateKey(const PluginID & ID, const RegistryPath & group, const RegistryPath & key) +{ + auto path = PrivateGroup(ID, group); + if (path.empty()) + { + return path; + } + + return path + key; +} + +// Sanitize the ID...not the best solution, but will suffice until this +// is converted to XML. We use base64 encoding to preserve case. +wxString PluginManager::ConvertID(const PluginID & ID) +{ + if (ID.StartsWith(wxT("base64:"))) + { + wxString id = ID.Mid(7); + ArrayOf buf{ id.length() / 4 * 3 }; + id = wxString::FromUTF8(buf.get(), b64decode(id, buf.get())); + return id; + } + + const wxCharBuffer & buf = ID.ToUTF8(); + return wxT("base64:") + b64encode(buf, strlen(buf)); +} + +//////////////////////////////////////////////////////////////////////////////// +// Base64 en/decoding +// +// Original routines marked as public domain and found at: +// +// http://en.wikibooks.org/wiki/Algorithm_implementation/Miscellaneous/Base64 +// +//////////////////////////////////////////////////////////////////////////////// + +// Lookup table for encoding +const static wxChar cset[] = wxT("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"); +const static char padc = wxT('='); + +wxString PluginManager::b64encode(const void *in, int len) +{ + unsigned char *p = (unsigned char *) in; + wxString out; + + unsigned long temp; + for (int i = 0; i < len / 3; i++) + { + temp = (*p++) << 16; //Convert to big endian + temp += (*p++) << 8; + temp += (*p++); + out += cset[(temp & 0x00FC0000) >> 18]; + out += cset[(temp & 0x0003F000) >> 12]; + out += cset[(temp & 0x00000FC0) >> 6]; + out += cset[(temp & 0x0000003F)]; + } + + switch (len % 3) + { + case 1: + temp = (*p++) << 16; //Convert to big endian + out += cset[(temp & 0x00FC0000) >> 18]; + out += cset[(temp & 0x0003F000) >> 12]; + out += padc; + out += padc; + break; + + case 2: + temp = (*p++) << 16; //Convert to big endian + temp += (*p++) << 8; + out += cset[(temp & 0x00FC0000) >> 18]; + out += cset[(temp & 0x0003F000) >> 12]; + out += cset[(temp & 0x00000FC0) >> 6]; + out += padc; + break; + } + + return out; +} + +int PluginManager::b64decode(const wxString &in, void *out) +{ + int len = in.length(); + unsigned char *p = (unsigned char *) out; + + if (len % 4) //Sanity check + { + return 0; + } + + int padding = 0; + if (len) + { + if (in[len - 1] == padc) + { + padding++; + } + + if (in[len - 2] == padc) + { + padding++; + } + } + + //const char *a = in.mb_str(); + //Setup a vector to hold the result + unsigned long temp = 0; //Holds decoded quanta + int i = 0; + while (i < len) + { + for (int quantumPosition = 0; quantumPosition < 4; quantumPosition++) + { + unsigned char c = in[i]; + temp <<= 6; + + if (c >= 0x41 && c <= 0x5A) + { + temp |= c - 0x41; + } + else if (c >= 0x61 && c <= 0x7A) + { + temp |= c - 0x47; + } + else if (c >= 0x30 && c <= 0x39) + { + temp |= c + 0x04; + } + else if (c == 0x2B) + { + temp |= 0x3E; + } + else if (c == 0x2F) + { + temp |= 0x3F; + } + else if (c == padc) + { + switch (len - i) + { + case 1: //One pad character + *p++ = (temp >> 16) & 0x000000FF; + *p++ = (temp >> 8) & 0x000000FF; + return p - (unsigned char *) out; + case 2: //Two pad characters + *p++ = (temp >> 10) & 0x000000FF; + return p - (unsigned char *) out; + } + } + i++; + } + *p++ = (temp >> 16) & 0x000000FF; + *p++ = (temp >> 8) & 0x000000FF; + *p++ = temp & 0x000000FF; + } + + return p - (unsigned char *) out; +} + +// This is defined out-of-line here, to keep ComponentInterface free of other +// #include directives. +TranslatableString ComponentInterface::GetName() +{ + return GetSymbol().Msgid(); +} From 710ec976ff19b2f1f2f8148aeba8831f48566043 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Tue, 27 Apr 2021 20:24:49 -0400 Subject: [PATCH 06/10] Move class PluginRegistrationDialog to a header file --- src/CMakeLists.txt | 1 + src/PluginManager.cpp | 61 +- src/PluginRegistrationDialog.h | 3110 +------------------------------- 3 files changed, 30 insertions(+), 3142 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 908e1deab..b0b88c6fe 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -202,6 +202,7 @@ list( APPEND SOURCES PlaybackSchedule.h PluginManager.cpp PluginManager.h + PluginRegistrationDialog.h Prefs.cpp Prefs.h Printing.cpp diff --git a/src/PluginManager.cpp b/src/PluginManager.cpp index d4600e322..7a18da43c 100644 --- a/src/PluginManager.cpp +++ b/src/PluginManager.cpp @@ -46,6 +46,7 @@ for shared and private configs - which need to move out. #include "FileNames.h" #include "ModuleManager.h" #include "PlatformCompatibility.h" +#include "PluginRegistrationDialog.h" #include "Prefs.h" #include "ShuttleGui.h" #include "wxFileNameWrapper.h" @@ -379,20 +380,6 @@ enum STATE_COUNT }; -struct ItemData -{ - std::vector plugs; - wxString name; - PluginPath path; - int state; - bool valid; - int nameWidth; - int pathWidth; - int stateWidth; -}; - -using ItemDataMap = std::unordered_map; - enum { ID_ShowAll = 10000, @@ -415,52 +402,6 @@ enum COL_COUNT }; -class PluginRegistrationDialog final : public wxDialogWrapper -{ -public: - // constructors and destructors - PluginRegistrationDialog(wxWindow *parent, EffectType type); - -private: - void Populate(); - void PopulateOrExchange(ShuttleGui & S); - void RegenerateEffectsList(int iShowWhat); - void SetState(int i, bool toggle, bool state = true); - - static int wxCALLBACK SortCompare(wxIntPtr item1, wxIntPtr item2, wxIntPtr sortData); - int SortCompare(ItemData *item1, ItemData *item2); - - void OnChangedVisibility(wxCommandEvent & evt); - void OnSort(wxListEvent & evt); - void DoSort( int col ); - void OnListChar(wxKeyEvent & evt); - void OnOK(wxCommandEvent & evt); - void OnCancel(wxCommandEvent & evt); - void OnSelectAll(wxCommandEvent & evt); - void OnClearAll(wxCommandEvent & evt); - void OnEnable(wxCommandEvent & evt); - void OnDisable(wxCommandEvent & evt); - -private: - EffectType mType; - int mFilter; - - wxArrayString mStates; - ItemDataMap mItems; - - int mSortColumn; - int mSortDirection; - - PluginPath mLongestPath; - - wxListCtrl *mEffects; -#if wxUSE_ACCESSIBILITY - CheckListAx *mAx; -#endif - - DECLARE_EVENT_TABLE() -}; - BEGIN_EVENT_TABLE(PluginRegistrationDialog, wxDialogWrapper) EVT_LIST_COL_CLICK(ID_List, PluginRegistrationDialog::OnSort) EVT_BUTTON(wxID_OK, PluginRegistrationDialog::OnOK) diff --git a/src/PluginRegistrationDialog.h b/src/PluginRegistrationDialog.h index d4600e322..4cd657a45 100644 --- a/src/PluginRegistrationDialog.h +++ b/src/PluginRegistrationDialog.h @@ -2,418 +2,24 @@ Audacity: A Digital Audio Editor - PluginManager.cpp + PluginRegistrationDialog.h - Leland Lucius + Paul Licameli split from PluginManager.cpp -*******************************************************************//*! +**********************************************************************/ +#ifndef __AUDACITY_PLUGIN_REGISTRATION_DIALOG__ +#define __AUDACITY_PLUGIN_REGISTRATION_DIALOG__ -\file PluginManager.cpp -\brief +#include "widgets/wxPanelWrapper.h" // to inherit +#include +#include // member -************************************************************************//** -\class PluginManager -\brief PluginManager maintains a list of all plug ins. That covers modules, -effects, generators, analysis-effects, commands. It also has functions -for shared and private configs - which need to move out. -*****************************************************************************/ - - -#include "PluginManager.h" - - - -#include - -#include // for wxUSE_* macros -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "audacity/EffectInterface.h" -#include "audacity/ModuleInterface.h" - -#include "AudacityFileConfig.h" -#include "FileNames.h" -#include "ModuleManager.h" -#include "PlatformCompatibility.h" -#include "Prefs.h" -#include "ShuttleGui.h" -#include "wxFileNameWrapper.h" -#include "widgets/AudacityMessageBox.h" -#include "widgets/ProgressDialog.h" - -#include - -// ============================================================================ -// -// -// -// ============================================================================ -#if wxUSE_ACCESSIBILITY -#include "widgets/WindowAccessible.h" - -class CheckListAx final : public WindowAccessible -{ -public: - CheckListAx(wxListCtrl * window); - - virtual ~ CheckListAx(); - - // Retrieves the address of an IDispatch interface for the specified child. - // All objects must support this property. - wxAccStatus GetChild( int childId, wxAccessible **child ) override; - - // Gets the number of children. - wxAccStatus GetChildCount( int *childCount ) override; - - // Gets the default action for this object (0) or > 0 (the action for a child). - // Return wxACC_OK even if there is no action. actionName is the action, or the empty - // string if there is no action. - // The retrieved string describes the action that is performed on an object, - // not what the object does as a result. For example, a toolbar button that prints - // a document has a default action of "Press" rather than "Prints the current document." - wxAccStatus GetDefaultAction( int childId, wxString *actionName ) override; - - // Returns the description for this object or a child. - wxAccStatus GetDescription( int childId, wxString *description ) override; - - // Gets the window with the keyboard focus. - // If childId is 0 and child is NULL, no object in - // this subhierarchy has the focus. - // If this object has the focus, child should be 'this'. - wxAccStatus GetFocus( int *childId, wxAccessible **child ) override; - - // Returns help text for this object or a child, similar to tooltip text. - wxAccStatus GetHelpText( int childId, wxString *helpText ) override; - - // Returns the keyboard shortcut for this object or child. - // Return e.g. ALT+K - wxAccStatus GetKeyboardShortcut( int childId, wxString *shortcut ) override; - - // Returns the rectangle for this object (id = 0) or a child element (id > 0). - // rect is in screen coordinates. - wxAccStatus GetLocation( wxRect& rect, int elementId ) override; - - // Gets the name of the specified object. - wxAccStatus GetName( int childId, wxString *name ) override; - - // Returns a role constant. - wxAccStatus GetRole( int childId, wxAccRole *role ) override; - - // Gets a variant representing the selected children - // of this object. - // Acceptable values: - // - a null variant (IsNull() returns TRUE) - // - a list variant (GetType() == wxT("list")) - // - an integer representing the selected child element, - // or 0 if this object is selected (GetType() == wxT("long")) - // - a "void*" pointer to a wxAccessible child object - wxAccStatus GetSelections( wxVariant *selections ) override; - - // Returns a state constant. - wxAccStatus GetState( int childId, long* state ) override; - - // Returns a localized string representing the value for the object - // or child. - wxAccStatus GetValue( int childId, wxString *strValue ) override; - - void SetSelected( int item, bool focused = true ); - -private: - wxListCtrl *mParent; - int mLastId; -}; - -CheckListAx::CheckListAx( wxListCtrl * window ) -: WindowAccessible( window ) -{ - mParent = window; - mLastId = -1; -} - -CheckListAx::~CheckListAx() -{ -} - -void CheckListAx::SetSelected( int item, bool focused ) -{ - if (mLastId != -1) - { - NotifyEvent( wxACC_EVENT_OBJECT_SELECTIONREMOVE, - mParent, - wxOBJID_CLIENT, - mLastId ); - mLastId = -1; - } - - if (item != -1) - { - if (focused) - { - NotifyEvent( wxACC_EVENT_OBJECT_FOCUS, - mParent, - wxOBJID_CLIENT, - item + 1 ); - } - - NotifyEvent( wxACC_EVENT_OBJECT_SELECTION, - mParent, - wxOBJID_CLIENT, - item + 1 ); - - mLastId = item + 1; - } -} - -// Retrieves the address of an IDispatch interface for the specified child. -// All objects must support this property. -wxAccStatus CheckListAx::GetChild( int childId, wxAccessible** child ) -{ - if( childId == wxACC_SELF ) - { - *child = this; - } - else - { - *child = NULL; - } - - return wxACC_OK; -} - -// Gets the number of children. -wxAccStatus CheckListAx::GetChildCount( int *childCount ) -{ - *childCount = mParent->GetItemCount(); - - return wxACC_OK; -} - -// Gets the default action for this object (0) or > 0 (the action for a child). -// Return wxACC_OK even if there is no action. actionName is the action, or the empty -// string if there is no action. -// The retrieved string describes the action that is performed on an object, -// not what the object does as a result. For example, a toolbar button that prints -// a document has a default action of "Press" rather than "Prints the current document." -wxAccStatus CheckListAx::GetDefaultAction( int WXUNUSED(childId), wxString *actionName ) -{ - actionName->clear(); - - return wxACC_OK; -} - -// Returns the description for this object or a child. -wxAccStatus CheckListAx::GetDescription( int WXUNUSED(childId), wxString *description ) -{ - description->clear(); - - return wxACC_OK; -} - -// Gets the window with the keyboard focus. -// If childId is 0 and child is NULL, no object in -// this subhierarchy has the focus. -// If this object has the focus, child should be 'this'. -wxAccStatus CheckListAx::GetFocus( int *childId, wxAccessible **child ) -{ - *childId = 0; - *child = this; - - return wxACC_OK; -} - -// Returns help text for this object or a child, similar to tooltip text. -wxAccStatus CheckListAx::GetHelpText( int WXUNUSED(childId), wxString *helpText ) -{ - helpText->clear(); - - return wxACC_OK; -} - -// Returns the keyboard shortcut for this object or child. -// Return e.g. ALT+K -wxAccStatus CheckListAx::GetKeyboardShortcut( int WXUNUSED(childId), wxString *shortcut ) -{ - shortcut->clear(); - - return wxACC_OK; -} - -// Returns the rectangle for this object (id = 0) or a child element (id > 0). -// rect is in screen coordinates. -wxAccStatus CheckListAx::GetLocation( wxRect& rect, int elementId ) -{ - if( elementId == wxACC_SELF ) - { - rect = mParent->GetRect(); - rect.SetPosition( mParent->GetParent()->ClientToScreen( rect.GetPosition() ) ); - } - else - { - if( elementId <= mParent->GetItemCount() ) - { - mParent->GetItemRect( elementId - 1, rect, wxLIST_RECT_LABEL ); - rect.SetPosition( mParent->ClientToScreen( rect.GetPosition() ) ); - } - } - - return wxACC_OK; -} - -// Gets the name of the specified object. -wxAccStatus CheckListAx::GetName( int WXUNUSED(childId), wxString *name ) -{ - *name = mParent->GetName(); - - return wxACC_OK; -} - -// Returns a role constant. -wxAccStatus CheckListAx::GetRole( int childId, wxAccRole *role ) -{ - if( childId == wxACC_SELF ) - { - *role = wxROLE_SYSTEM_LIST; - } - else - { - *role = wxROLE_SYSTEM_LISTITEM; - } - - return wxACC_OK; -} - -// Gets a variant representing the selected children -// of this object. -// Acceptable values: -// - a null variant (IsNull() returns TRUE) -// - a list variant (GetType() == wxT("list")) -// - an integer representing the selected child element, -// or 0 if this object is selected (GetType() == wxT("long")) -// - a "void*" pointer to a wxAccessible child object -wxAccStatus CheckListAx::GetSelections( wxVariant * WXUNUSED(selections) ) -{ - return wxACC_NOT_IMPLEMENTED; -} - -// Returns a state constant. -wxAccStatus CheckListAx::GetState( int childId, long *pState ) -{ - int flag = wxACC_STATE_SYSTEM_FOCUSABLE; - - if( childId == wxACC_SELF ) - { - flag |= wxACC_STATE_SYSTEM_FOCUSED; - } - else - { - wxListItem item; - - item.SetId( childId - 1 ); - item.SetState( wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED ); - item.SetMask( wxLIST_MASK_STATE ); - - if( mParent->GetItem( item ) ) - { - flag |= wxACC_STATE_SYSTEM_SELECTABLE; - - long state = item.GetState(); - - if( state & wxLIST_STATE_FOCUSED ) - { - flag |= wxACC_STATE_SYSTEM_FOCUSED; - } - - if( state & wxLIST_STATE_SELECTED ) - { - flag |= wxACC_STATE_SYSTEM_SELECTED; - } - } - } - - *pState = flag; - - return wxACC_OK; -} - -// Returns a localized string representing the value for the object -// or child. -wxAccStatus CheckListAx::GetValue( int childId, wxString *strValue ) -{ - if( childId == 0 ) - { - return wxACC_OK; - } - else - { - *strValue = mParent->GetItemText( childId - 1 ); - } - - return wxACC_OK; -} - -#endif - -// ============================================================================ -// -// -// -// ============================================================================ - -enum -{ - STATE_Enabled, - STATE_Disabled, - STATE_New, - - STATE_COUNT -}; - -struct ItemData -{ - std::vector plugs; - wxString name; - PluginPath path; - int state; - bool valid; - int nameWidth; - int pathWidth; - int stateWidth; -}; - -using ItemDataMap = std::unordered_map; - -enum -{ - ID_ShowAll = 10000, - ID_ShowEnabled, - ID_ShowDisabled, - ID_ShowNew, - ID_List, - ID_ClearAll, - ID_SelectAll, - ID_Enable, - ID_Disable, -}; - -enum -{ - COL_Name, - COL_State, - COL_Path, - - COL_COUNT -}; +class CheckListAx; +enum EffectType : int; +class PluginDescriptor; +class ShuttleGui; +class wxListEvent; +class wxListCtrl; class PluginRegistrationDialog final : public wxDialogWrapper { @@ -422,6 +28,20 @@ public: PluginRegistrationDialog(wxWindow *parent, EffectType type); private: + struct ItemData + { + std::vector plugs; + wxString name; + PluginPath path; + int state; + bool valid; + int nameWidth; + int pathWidth; + int stateWidth; + }; + + using ItemDataMap = std::unordered_map; + void Populate(); void PopulateOrExchange(ShuttleGui & S); void RegenerateEffectsList(int iShowWhat); @@ -461,2679 +81,5 @@ private: DECLARE_EVENT_TABLE() }; -BEGIN_EVENT_TABLE(PluginRegistrationDialog, wxDialogWrapper) - EVT_LIST_COL_CLICK(ID_List, PluginRegistrationDialog::OnSort) - EVT_BUTTON(wxID_OK, PluginRegistrationDialog::OnOK) - EVT_BUTTON(wxID_CANCEL, PluginRegistrationDialog::OnCancel) - EVT_BUTTON(ID_ClearAll, PluginRegistrationDialog::OnClearAll) - EVT_BUTTON(ID_SelectAll, PluginRegistrationDialog::OnSelectAll) - EVT_BUTTON(ID_Enable, PluginRegistrationDialog::OnEnable) - EVT_BUTTON(ID_Disable, PluginRegistrationDialog::OnDisable) - EVT_RADIOBUTTON(ID_ShowAll, PluginRegistrationDialog::OnChangedVisibility) - EVT_RADIOBUTTON(ID_ShowEnabled, PluginRegistrationDialog::OnChangedVisibility) - EVT_RADIOBUTTON(ID_ShowDisabled, PluginRegistrationDialog::OnChangedVisibility) - EVT_RADIOBUTTON(ID_ShowNew, PluginRegistrationDialog::OnChangedVisibility) -END_EVENT_TABLE() -PluginRegistrationDialog::PluginRegistrationDialog(wxWindow *parent, EffectType type) -: wxDialogWrapper(parent, - wxID_ANY, - XO("Manage Plug-ins"), - wxDefaultPosition, wxDefaultSize, - wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) -{ - mType = type; - mEffects = NULL; - SetName(); - - mStates.resize(STATE_COUNT); - mStates[STATE_Enabled] = _("Enabled"); - mStates[STATE_Disabled] = _("Disabled"); - mStates[STATE_New] = _("New"); - - mSortColumn = COL_Name; - mSortDirection = 1; - - Populate(); - - DoSort( mSortColumn ); -} - -void PluginRegistrationDialog::Populate() -{ - //------------------------- Main section -------------------- - ShuttleGui S(this, eIsCreating); - PopulateOrExchange(S); - // ----------------------- End of main section -------------- -} - -/// Defines the dialog and does data exchange with it. -void PluginRegistrationDialog::PopulateOrExchange(ShuttleGui &S) -{ - S.StartVerticalLay(true); - { - /*i18n-hint: The dialog shows a list of plugins with check-boxes - beside each one.*/ -// S.StartStatic(XO("Effects"), true); - S.StartVerticalLay(); - { - S.StartHorizontalLay(wxEXPAND, 0); - { - S.StartHorizontalLay(wxALIGN_LEFT, 0); - { - S.AddPrompt(XXO("Select effects, click the Enable or Disable button, then click OK.")); - } - S.EndHorizontalLay(); - - S.StartHorizontalLay(wxCENTER, 1); - { - S.AddSpace(1); - } - S.EndHorizontalLay(); - - S.StartHorizontalLay(wxALIGN_NOT | wxALIGN_LEFT, 0); - { - wxRadioButton *rb; - - /* i18n-hint: This is before radio buttons selecting which effects to show */ - S.AddPrompt(XXO("Show:")); - rb = S.Id(ID_ShowAll) - /* i18n-hint: Radio button to show all effects */ - .Name(XO("Show all")) - /* i18n-hint: Radio button to show all effects */ - .AddRadioButton(XXO("&All")); -#if wxUSE_ACCESSIBILITY - // so that name can be set on a standard control - rb->SetAccessible(safenew WindowAccessible(rb)); #endif - - rb = S.Id(ID_ShowDisabled) - /* i18n-hint: Radio button to show just the currently disabled effects */ - .Name(XO("Show disabled")) - /* i18n-hint: Radio button to show just the currently disabled effects */ - .AddRadioButtonToGroup(XXO("D&isabled")); -#if wxUSE_ACCESSIBILITY - // so that name can be set on a standard control - rb->SetAccessible(safenew WindowAccessible(rb)); -#endif - - rb = S.Id(ID_ShowEnabled) - /* i18n-hint: Radio button to show just the currently enabled effects */ - .Name(XO("Show enabled")) - /* i18n-hint: Radio button to show just the currently enabled effects */ - .AddRadioButtonToGroup(XXO("E&nabled")); -#if wxUSE_ACCESSIBILITY - // so that name can be set on a standard control - rb->SetAccessible(safenew WindowAccessible(rb)); -#endif - - rb = S.Id(ID_ShowNew) - /* i18n-hint: Radio button to show just the newly discovered effects */ - .Name(XO("Show new")) - /* i18n-hint: Radio button to show just the newly discovered effects */ - .AddRadioButtonToGroup(XXO("Ne&w")); -#if wxUSE_ACCESSIBILITY - // so that name can be set on a standard control - rb->SetAccessible(safenew WindowAccessible(rb)); -#endif - } - S.EndHorizontalLay(); - } - S.EndHorizontalLay(); - - mEffects = S.Id(ID_List) - .Style(wxSUNKEN_BORDER | wxLC_REPORT | wxLC_HRULES | wxLC_VRULES ) - .ConnectRoot(wxEVT_KEY_DOWN, - &PluginRegistrationDialog::OnListChar) - .AddListControlReportMode({ XO("Name"), XO("State"), XO("Path") }); -#if wxUSE_ACCESSIBILITY - mEffects->SetAccessible(mAx = safenew CheckListAx(mEffects)); -#endif - - S.StartHorizontalLay(wxALIGN_LEFT | wxEXPAND, 0); - { - S.Id(ID_SelectAll).AddButton(XXO("&Select All")); - S.Id(ID_ClearAll).AddButton(XXO("C&lear All")); - - S.StartHorizontalLay(wxALIGN_CENTER); - { - S.AddSpace(1); - } - S.EndHorizontalLay(); - - S.Id(ID_Enable).AddButton(XXO("&Enable")); - S.Id(ID_Disable).AddButton(XXO("&Disable")); - } - S.EndHorizontalLay(); - } -// S.EndStatic(); - S.EndVerticalLay(); - - S.AddStandardButtons(eOkButton | eCancelButton); - } - S.EndVerticalLay(); - - std::vector colWidths; - for (int i = 0, cnt = mEffects->GetColumnCount(); i < cnt; i++) - { - colWidths.push_back(0); - } - - for (int i = 0, cnt = mStates.size(); i < cnt; i++) - { - int x; - mEffects->GetTextExtent(mStates[i], &x, NULL); - colWidths[COL_State] = wxMax(colWidths[COL_State], x + 4); // 2 pixel margin on each side - } - - PluginManager & pm = PluginManager::Get(); - for (auto &plug : pm.AllPlugins()) { - PluginType plugType = plug.GetPluginType(); - if (plugType != PluginTypeEffect && plugType != PluginTypeStub) - continue; - - const auto &path = plug.GetPath(); - ItemData & item = mItems[path]; // will create NEW entry - item.plugs.push_back(&plug); - item.path = path; - item.state = plug.IsEnabled() ? STATE_Enabled : STATE_Disabled; - item.valid = plug.IsValid(); - - if (plugType == PluginTypeEffect) - { - item.name = plug.GetSymbol().Translation(); - } - // This is not right and will not work when other plugin types are added. - // But it's presumed that the plugin manager dialog will be fully developed - // by then. - else if (plugType == PluginTypeStub) - { - wxFileName fname { path }; - item.name = fname.GetName().Trim(false).Trim(true); - if (!item.valid) - { - item.state = STATE_New; - } - } - - int x; - mEffects->GetTextExtent(item.name, &x, NULL); - colWidths[COL_Name] = wxMax(colWidths[COL_Name], x); - - mEffects->GetTextExtent(item.path, &x, NULL); - if (x > colWidths[COL_Path]) - { - mLongestPath = item.path; - } - colWidths[COL_Path] = wxMax(colWidths[COL_Path], x); - } - - wxRect r = wxGetClientDisplayRect(); - - int maxW = 0; - for (int i = 0, cnt = mEffects->GetColumnCount(); i < cnt; i++) - { - int w = colWidths[i] + /* fudge */ 10; - mEffects->SetColumnWidth(i, w); - maxW += w; - } - - // Keep dialog from getting too wide - int w = r.GetWidth() - (GetClientSize().GetWidth() - mEffects->GetSize().GetWidth()); - mEffects->SetMinSize({ std::min(maxW, w), 200 }); - mEffects->SetMaxSize({ w, -1 }); - - RegenerateEffectsList(ID_ShowAll); - - Layout(); - Fit(); - - wxSize sz = GetSize(); - sz.SetWidth(wxMin(sz.GetWidth(), r.GetWidth())); - sz.SetHeight(wxMin(sz.GetHeight(), r.GetHeight())); - SetMinSize(sz); - - // Parent window is usually not there yet, so centre on screen rather than on parent. - CenterOnScreen(); - - if (mEffects->GetItemCount() > 0) - { - // Make sure first item is selected/focused. - mEffects->SetFocus(); - mEffects->SetItemState(0, wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED, wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED); -#if wxUSE_ACCESSIBILITY - mAx->SetSelected(0); -#endif - } - -} - -void PluginRegistrationDialog::RegenerateEffectsList(int filter) -{ - mFilter = filter; - - mEffects->DeleteAllItems(); - - int i = 0; - for (ItemDataMap::iterator iter = mItems.begin(); iter != mItems.end(); ++iter) - { - ItemData & item = iter->second; - bool add = false; - - switch (mFilter) - { - case ID_ShowAll: - add = true; - break; - case ID_ShowNew: - if (item.state == STATE_New) - { - add = true; - } - break; - case ID_ShowEnabled: - if (item.state == STATE_Enabled) - { - add = true; - } - break; - case ID_ShowDisabled: - if (item.state == STATE_Disabled) - { - add = true; - } - break; - } - - if (add) - { - mEffects->InsertItem(i, item.name); - mEffects->SetItem(i, COL_State, mStates[item.state]); - mEffects->SetItem(i, COL_Path, item.path); - mEffects->SetItemPtrData(i, (wxUIntPtr) &item); - - ++i; - } - } - - mEffects->SortItems(SortCompare, (wxUIntPtr) this); - - if (mEffects->GetItemCount() > 0) - { - // Make sure first item is selected/focused. -// mEffects->SetFocus(); - mEffects->SetItemState(0, wxLIST_STATE_FOCUSED|wxLIST_STATE_SELECTED, wxLIST_STATE_FOCUSED|wxLIST_STATE_SELECTED); -#if wxUSE_ACCESSIBILITY - mAx->SetSelected(0, false); -#endif - } -} - -void PluginRegistrationDialog::SetState(int i, bool toggle, bool state) -{ - wxListItem li; - - li.m_mask = wxLIST_MASK_DATA; - li.m_itemId = i; - - mEffects->GetItem(li); - - ItemData *item = (ItemData *) li.m_data; - - // If changing the state of a "New" (stub) entry, then we mark it as valid - // since it will either be registered if "Enabled" or ignored if "Disabled". - if (item->state == STATE_New) - { - item->valid = true; - } - - if (toggle) - { - item->state = item->state == STATE_Enabled ? STATE_Disabled : STATE_Enabled; - } - else - { - item->state = state; - } - - if (mFilter == ID_ShowNew && item->state != STATE_New) - { - mEffects->DeleteItem(i); - } - else if (mFilter == ID_ShowDisabled && item->state != STATE_Disabled) - { - mEffects->DeleteItem(i); - } - else if (mFilter == ID_ShowEnabled && item->state != STATE_Enabled) - { - mEffects->DeleteItem(i); - } - else - { - mEffects->SetItem(i, COL_State, mStates[item->state]); -#if wxUSE_ACCESSIBILITY - mAx->SetSelected(i); -#endif - } -} - -int wxCALLBACK PluginRegistrationDialog::SortCompare(wxIntPtr item1, wxIntPtr item2, wxIntPtr sortData) -{ - PluginRegistrationDialog *dlg = (PluginRegistrationDialog *) sortData; - ItemData *i1 = (ItemData *) item1; - ItemData *i2 = (ItemData *) item2; - - return dlg->SortCompare(i1, i2); -} - -int PluginRegistrationDialog::SortCompare(ItemData *item1, ItemData *item2) -{ - // This function is a three-valued comparator - - wxString *str1; - wxString *str2; - - switch (mSortColumn) - { - case COL_Name: - str1 = &item1->name; - str2 = &item2->name; - break; - case COL_State: - str1 = &mStates[item1->state]; - str2 = &mStates[item2->state]; - break; - case COL_Path: - str1 = &item1->path; - str2 = &item2->path; - break; - default: - return 0; - } - - return str2->CmpNoCase(*str1) * mSortDirection; -} - -void PluginRegistrationDialog::OnChangedVisibility(wxCommandEvent & evt) -{ - // Go and show the relevant items. - RegenerateEffectsList(evt.GetId()); -} - -void PluginRegistrationDialog::OnSort(wxListEvent & evt) -{ - int col = evt.GetColumn(); - DoSort( col ); -} - -void PluginRegistrationDialog::DoSort( int col ) -{ - if (col != mSortColumn) - { - mSortDirection = 1; - } - else - { - mSortDirection *= -1; - } - - mSortColumn = col; - mEffects->SortItems(SortCompare, (wxUIntPtr) this); - - // Without a refresh, wxMac doesn't redisplay the list properly after a sort - mEffects->Refresh(); -} - -void PluginRegistrationDialog::OnListChar(wxKeyEvent & evt) -{ - switch (evt.GetKeyCode()) - { - case WXK_SPACE: - { - int item = mEffects->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_FOCUSED); - if (item != wxNOT_FOUND) - { - SetState(item, true); - } - } - break; - - case WXK_RETURN: - // Don't know why wxListCtrls prevent default dialog action, - // but they do, so handle it. - EmulateButtonClickIfPresent(GetAffirmativeId()); - break; - - default: - evt.Skip(); - break; - } -} - -void PluginRegistrationDialog::OnSelectAll(wxCommandEvent & WXUNUSED(evt)) -{ - for (int i = 0, cnt = mEffects->GetItemCount(); i < cnt; i++) - { - mEffects->SetItemState(i, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); - } -} - -void PluginRegistrationDialog::OnClearAll(wxCommandEvent & WXUNUSED(evt)) -{ - for (int i = 0, cnt = mEffects->GetItemCount(); i < cnt; i++) - { - mEffects->SetItemState(i, 0, wxLIST_STATE_SELECTED); - } -} - -void PluginRegistrationDialog::OnEnable(wxCommandEvent & WXUNUSED(evt)) -{ - std::vector items; - - { - long i = mEffects->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); - while (i != wxNOT_FOUND) - { - items.insert(items.begin(), i); - i = mEffects->GetNextItem(i, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); - } - } - - for (size_t i = 0, cnt = items.size(); i < cnt; i++) - { - SetState(items[i], false, STATE_Enabled); - } -} - -void PluginRegistrationDialog::OnDisable(wxCommandEvent & WXUNUSED(evt)) -{ - std::vector items; - - { - long i = mEffects->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); - while (i != wxNOT_FOUND) - { - items.insert(items.begin(), i); - i = mEffects->GetNextItem(i, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); - } - } - - for (size_t i = 0, cnt = items.size(); i < cnt; i++) - { - SetState(items[i], false, STATE_Disabled); - } -} - -void PluginRegistrationDialog::OnOK(wxCommandEvent & WXUNUSED(evt)) -{ - PluginManager & pm = PluginManager::Get(); - ModuleManager & mm = ModuleManager::Get(); - - int enableCount = 0; - for (ItemDataMap::iterator iter = mItems.begin(); iter != mItems.end(); ++iter) - { - ItemData & item = iter->second; - wxString path = item.path; - - if (item.state == STATE_Enabled && item.plugs[0]->GetPluginType() == PluginTypeStub) - { - enableCount++; - } - } - - wxString last3 = mLongestPath + wxT("\n") + - mLongestPath + wxT("\n") + - mLongestPath + wxT("\n"); - - auto msg = XO("Enabling effects or commands:\n\n%s").Format( last3 ); - - // Make sure the progress dialog is deleted before we call EndModal() or - // we will leave the project window in an unusable state on OSX. - // See bug #1192. - { - ProgressDialog progress{ - Verbatim( GetTitle() ), msg, pdlgHideStopButton }; - progress.CenterOnParent(); - - int i = 0; - for (ItemDataMap::iterator iter = mItems.begin(); iter != mItems.end(); ++iter) - { - ItemData & item = iter->second; - wxString path = item.path; - - if (item.state == STATE_Enabled && item.plugs[0]->GetPluginType() == PluginTypeStub) - { - last3 = last3.AfterFirst(wxT('\n')) + item.path + wxT("\n"); - auto status = progress.Update(++i, enableCount, - XO("Enabling effect or command:\n\n%s").Format( last3 )); - if (status == ProgressResult::Cancelled) - { - break; - } - - TranslatableString errMsgs; - - // Try to register the plugin via each provider until one succeeds - for (size_t j = 0, cntj = item.plugs.size(); j < cntj; j++) - { - TranslatableString errMsg; - if (mm.RegisterEffectPlugin(item.plugs[j]->GetProviderID(), path, - errMsg)) - { - for (auto plug : item.plugs) - pm.UnregisterPlugin( - plug->GetProviderID() + wxT("_") + path); - // Bug 1893. We've found a provider that works. - // Error messages from any that failed are no longer useful. - errMsgs = {}; - break; - } - else - { - if (!errMsgs.empty()) - errMsgs.Join( errMsg, '\n' ); - else - errMsgs = errMsg; - } - } - if (!errMsgs.empty()) - AudacityMessageBox( - XO("Effect or Command at %s failed to register:\n%s") - .Format( path, errMsgs ) ); - } - else if (item.state == STATE_New) { - for (auto plug : item.plugs) - plug->SetValid(false); - } - else if (item.state != STATE_New) { - for (auto plug : item.plugs) { - plug->SetEnabled(item.state == STATE_Enabled); - plug->SetValid(item.valid); - } - } - } - - pm.Save(); - } - - EndModal(wxID_OK); -} - -void PluginRegistrationDialog::OnCancel(wxCommandEvent & WXUNUSED(evt)) -{ - EndModal(wxID_CANCEL); -} - - - -/////////////////////////////////////////////////////////////////////////////// -// -// Plugindescriptor -// -/////////////////////////////////////////////////////////////////////////////// - -PluginDescriptor::PluginDescriptor() -{ - mPluginType = PluginTypeNone; - mEnabled = false; - mValid = false; - mInstance = nullptr; - - mEffectType = EffectTypeNone; - mEffectInteractive = false; - mEffectDefault = false; - mEffectLegacy = false; - mEffectRealtime = false; - mEffectAutomatable = false; -} - -PluginDescriptor::~PluginDescriptor() -{ -} - -PluginDescriptor &PluginDescriptor::operator =(PluginDescriptor &&) = default; - -bool PluginDescriptor::IsInstantiated() const -{ - return mInstance != nullptr; -} - -ComponentInterface *PluginDescriptor::GetInstance() -{ - if (!mInstance) - { - if (GetPluginType() == PluginTypeModule) - mInstance = ModuleManager::Get().CreateProviderInstance(GetID(), GetPath()); - else - { - muInstance = ModuleManager::Get().CreateInstance(GetProviderID(), GetPath()); - mInstance = muInstance.get(); - } - } - - return mInstance; -} - -void PluginDescriptor::SetInstance(std::unique_ptr instance) -{ - muInstance = std::move(instance); - mInstance = muInstance.get(); -} - -PluginType PluginDescriptor::GetPluginType() const -{ - return mPluginType; -} - -const PluginID & PluginDescriptor::GetID() const -{ - return mID; -} - -const PluginID & PluginDescriptor::GetProviderID() const -{ - return mProviderID; -} - -const PluginPath & PluginDescriptor::GetPath() const -{ - return mPath; -} - -const ComponentInterfaceSymbol & PluginDescriptor::GetSymbol() const -{ - return mSymbol; -} - -wxString PluginDescriptor::GetUntranslatedVersion() const -{ - return mVersion; -} - -wxString PluginDescriptor::GetVendor() const -{ - return mVendor; -} - -bool PluginDescriptor::IsEnabled() const -{ - return mEnabled; -} - -bool PluginDescriptor::IsValid() const -{ - return mValid; -} - -void PluginDescriptor::SetPluginType(PluginType type) -{ - mPluginType = type; -} - -void PluginDescriptor::SetID(const PluginID & ID) -{ - mID = ID; -} - -void PluginDescriptor::SetProviderID(const PluginID & providerID) -{ - mProviderID = providerID; -} - -void PluginDescriptor::SetPath(const PluginPath & path) -{ - mPath = path; -} - -void PluginDescriptor::SetSymbol(const ComponentInterfaceSymbol & symbol) -{ - mSymbol = symbol; -} - -void PluginDescriptor::SetVersion(const wxString & version) -{ - mVersion = version; -} - -void PluginDescriptor::SetVendor(const wxString & vendor) -{ - mVendor = vendor; -} - -void PluginDescriptor::SetEnabled(bool enable) -{ - mEnabled = enable; -} - -void PluginDescriptor::SetValid(bool valid) -{ - mValid = valid; -} - -// Effects - -wxString PluginDescriptor::GetEffectFamily() const -{ - return mEffectFamily; -} - -EffectType PluginDescriptor::GetEffectType() const -{ - return mEffectType; -} - -bool PluginDescriptor::IsEffectInteractive() const -{ - return mEffectInteractive; -} - -bool PluginDescriptor::IsEffectDefault() const -{ - return mEffectDefault; -} - -bool PluginDescriptor::IsEffectLegacy() const -{ - return mEffectLegacy; -} - -bool PluginDescriptor::IsEffectRealtime() const -{ - return mEffectRealtime; -} - -bool PluginDescriptor::IsEffectAutomatable() const -{ - return mEffectAutomatable; -} - -void PluginDescriptor::SetEffectFamily(const wxString & family) -{ - mEffectFamily = family; -} - -void PluginDescriptor::SetEffectType(EffectType type) -{ - mEffectType = type; -} - -void PluginDescriptor::SetEffectInteractive(bool interactive) -{ - mEffectInteractive = interactive; -} - -void PluginDescriptor::SetEffectDefault(bool dflt) -{ - mEffectDefault = dflt; -} - -void PluginDescriptor::SetEffectLegacy(bool legacy) -{ - mEffectLegacy = legacy; -} - -void PluginDescriptor::SetEffectRealtime(bool realtime) -{ - mEffectRealtime = realtime; -} - -void PluginDescriptor::SetEffectAutomatable(bool automatable) -{ - mEffectAutomatable = automatable; -} - -// Importer - -const wxString & PluginDescriptor::GetImporterIdentifier() const -{ - return mImporterIdentifier; -} - -void PluginDescriptor::SetImporterIdentifier(const wxString & identifier) -{ - mImporterIdentifier = identifier; -} - -const FileExtensions & PluginDescriptor::GetImporterExtensions() - const -{ - return mImporterExtensions; -} - -void PluginDescriptor::SetImporterExtensions( FileExtensions extensions ) -{ - mImporterExtensions = std::move( extensions ); -} - -/////////////////////////////////////////////////////////////////////////////// -// -// PluginManager -// -/////////////////////////////////////////////////////////////////////////////// - -// Registry has the list of plug ins -#define REGVERKEY wxString(wxT("/pluginregistryversion")) -#define REGVERCUR wxString(wxT("1.1")) -#define REGROOT wxString(wxT("/pluginregistry/")) - -// Settings has the values of the plug in settings. -#define SETVERKEY wxString(wxT("/pluginsettingsversion")) -#define SETVERCUR wxString(wxT("1.0")) -#define SETROOT wxString(wxT("/pluginsettings/")) - -#define KEY_ID wxT("ID") -#define KEY_PATH wxT("Path") -#define KEY_SYMBOL wxT("Symbol") -#define KEY_NAME wxT("Name") -#define KEY_VENDOR wxT("Vendor") -#define KEY_VERSION wxT("Version") -#define KEY_DESCRIPTION wxT("Description") -#define KEY_LASTUPDATED wxT("LastUpdated") -#define KEY_ENABLED wxT("Enabled") -#define KEY_VALID wxT("Valid") -#define KEY_PROVIDERID wxT("ProviderID") -#define KEY_EFFECTTYPE wxT("EffectType") -#define KEY_EFFECTFAMILY wxT("EffectFamily") -#define KEY_EFFECTDEFAULT wxT("EffectDefault") -#define KEY_EFFECTINTERACTIVE wxT("EffectInteractive") -#define KEY_EFFECTREALTIME wxT("EffectRealtime") -#define KEY_EFFECTAUTOMATABLE wxT("EffectAutomatable") -#define KEY_EFFECTTYPE_NONE wxT("None") -#define KEY_EFFECTTYPE_ANALYZE wxT("Analyze") -#define KEY_EFFECTTYPE_GENERATE wxT("Generate") -#define KEY_EFFECTTYPE_PROCESS wxT("Process") -#define KEY_EFFECTTYPE_TOOL wxT("Tool") -#define KEY_EFFECTTYPE_HIDDEN wxT("Hidden") -#define KEY_IMPORTERIDENT wxT("ImporterIdent") -//#define KEY_IMPORTERFILTER wxT("ImporterFilter") -#define KEY_IMPORTEREXTENSIONS wxT("ImporterExtensions") - -// ============================================================================ -// -// PluginManagerInterface implementation -// -// ============================================================================ - -const PluginID &PluginManagerInterface::DefaultRegistrationCallback( - ModuleInterface *provider, ComponentInterface *pInterface ) -{ - EffectDefinitionInterface * pEInterface = dynamic_cast(pInterface); - if( pEInterface ) - return PluginManager::Get().RegisterPlugin(provider, pEInterface, PluginTypeEffect); - ComponentInterface * pCInterface = dynamic_cast(pInterface); - if( pCInterface ) - return PluginManager::Get().RegisterPlugin(provider, pCInterface); - static wxString empty; - return empty; -} - -const PluginID &PluginManagerInterface::AudacityCommandRegistrationCallback( - ModuleInterface *provider, ComponentInterface *pInterface ) -{ - ComponentInterface * pCInterface = dynamic_cast(pInterface); - if( pCInterface ) - return PluginManager::Get().RegisterPlugin(provider, pCInterface); - static wxString empty; - return empty; -} - -RegistryPath PluginManager::GetPluginEnabledSetting( const PluginID &ID ) const -{ - auto pPlugin = GetPlugin( ID ); - if ( pPlugin ) - return GetPluginEnabledSetting( *pPlugin ); - return {}; -} - -RegistryPath PluginManager::GetPluginEnabledSetting( - const PluginDescriptor &desc ) const -{ - switch ( desc.GetPluginType() ) { - case PluginTypeModule: { - // Retrieve optional family symbol that was recorded in - // RegisterPlugin() for the module - auto family = desc.GetEffectFamily(); - if ( family.empty() ) // as for built-in effect and command modules - return {}; - else - return wxT('/') + family + wxT("/Enable"); - } - case PluginTypeEffect: - // do NOT use GetEffectFamily() for this descriptor, but instead, - // delegate to the plugin descriptor of the provider, which may - // be different (may be empty) - return GetPluginEnabledSetting( desc.GetProviderID() ); - default: - return {}; - } -} - -bool PluginManager::IsPluginRegistered( - const PluginPath &path, const TranslatableString *pName) -{ - for (auto &pair : mPlugins) { - if (auto &descriptor = pair.second; descriptor.GetPath() == path) { - if (pName) - descriptor.SetSymbol( - { descriptor.GetSymbol().Internal(), *pName }); - return true; - } - } - return false; -} - -const PluginID & PluginManager::RegisterPlugin(ModuleInterface *module) -{ - PluginDescriptor & plug = CreatePlugin(GetID(module), module, PluginTypeModule); - plug.SetEffectFamily(module->GetOptionalFamilySymbol().Internal()); - - plug.SetEnabled(true); - plug.SetValid(true); - - return plug.GetID(); -} - -const PluginID & PluginManager::RegisterPlugin(ModuleInterface *provider, ComponentInterface *command) -{ - PluginDescriptor & plug = CreatePlugin(GetID(command), command, (PluginType)PluginTypeAudacityCommand); - - plug.SetProviderID(PluginManager::GetID(provider)); - - plug.SetEnabled(true); - plug.SetValid(true); - - return plug.GetID(); -} - -const PluginID & PluginManager::RegisterPlugin(ModuleInterface *provider, EffectDefinitionInterface *effect, int type) -{ - PluginDescriptor & plug = CreatePlugin(GetID(effect), effect, (PluginType)type); - - plug.SetProviderID(PluginManager::GetID(provider)); - - plug.SetEffectType(effect->GetClassification()); - plug.SetEffectFamily(effect->GetFamily().Internal()); - plug.SetEffectInteractive(effect->IsInteractive()); - plug.SetEffectDefault(effect->IsDefault()); - plug.SetEffectRealtime(effect->SupportsRealtime()); - plug.SetEffectAutomatable(effect->SupportsAutomation()); - - plug.SetEnabled(true); - plug.SetValid(true); - - return plug.GetID(); -} - -const PluginID & PluginManager::RegisterPlugin(ModuleInterface *provider, ImporterInterface *importer) -{ - PluginDescriptor & plug = CreatePlugin(GetID(importer), importer, PluginTypeImporter); - - plug.SetProviderID(PluginManager::GetID(provider)); - - plug.SetImporterIdentifier(importer->GetPluginStringID()); - plug.SetImporterExtensions(importer->GetSupportedExtensions()); - - return plug.GetID(); -} - -void PluginManager::FindFilesInPathList(const wxString & pattern, - const FilePaths & pathList, - FilePaths & files, - bool directories) -{ - - wxLogNull nolog; - - // Why bother... - if (pattern.empty()) - { - return; - } - - // TODO: We REALLY need to figure out the "Audacity" plug-in path(s) - - FilePaths paths; - - // Add the "per-user" plug-ins directory - { - const wxFileName &ff = FileNames::PlugInDir(); - paths.push_back(ff.GetFullPath()); - } - - // Add the "Audacity" plug-ins directory - wxFileName ff = PlatformCompatibility::GetExecutablePath(); -#if defined(__WXMAC__) - // Path ends for example in "Audacity.app/Contents/MacOSX" - //ff.RemoveLastDir(); - //ff.RemoveLastDir(); - // just remove the MacOSX part. - ff.RemoveLastDir(); -#endif - ff.AppendDir(wxT("plug-ins")); - paths.push_back(ff.GetPath()); - - // Weed out duplicates - for (const auto &filePath : pathList) - { - ff = filePath; - const wxString path{ ff.GetFullPath() }; - if (paths.Index(path, wxFileName::IsCaseSensitive()) == wxNOT_FOUND) - { - paths.push_back(path); - } - } - - // Find all matching files in each path - for (size_t i = 0, cnt = paths.size(); i < cnt; i++) - { - ff = paths[i] + wxFILE_SEP_PATH + pattern; - wxDir::GetAllFiles(ff.GetPath(), &files, ff.GetFullName(), directories ? wxDIR_DEFAULT : wxDIR_FILES); - } - - return; -} - -bool PluginManager::HasSharedConfigGroup(const PluginID & ID, const RegistryPath & group) -{ - return HasGroup(SharedGroup(ID, group)); -} - -bool PluginManager::GetSharedConfigSubgroups(const PluginID & ID, const RegistryPath & group, RegistryPaths & subgroups) -{ - return GetSubgroups(SharedGroup(ID, group), subgroups); -} - -bool PluginManager::GetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, wxString & value, const wxString & defval) -{ - return GetConfig(SharedKey(ID, group, key), value, defval); -} - -bool PluginManager::GetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, int & value, int defval) -{ - return GetConfig(SharedKey(ID, group, key), value, defval); -} - -bool PluginManager::GetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, bool & value, bool defval) -{ - return GetConfig(SharedKey(ID, group, key), value, defval); -} - -bool PluginManager::GetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, float & value, float defval) -{ - return GetConfig(SharedKey(ID, group, key), value, defval); -} - -bool PluginManager::GetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, double & value, double defval) -{ - return GetConfig(SharedKey(ID, group, key), value, defval); -} - -bool PluginManager::SetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const wxString & value) -{ - return SetConfig(SharedKey(ID, group, key), value); -} - -bool PluginManager::SetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const int & value) -{ - return SetConfig(SharedKey(ID, group, key), value); -} - -bool PluginManager::SetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const bool & value) -{ - return SetConfig(SharedKey(ID, group, key), value); -} - -bool PluginManager::SetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const float & value) -{ - return SetConfig(SharedKey(ID, group, key), value); -} - -bool PluginManager::SetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const double & value) -{ - return SetConfig(SharedKey(ID, group, key), value); -} - -bool PluginManager::RemoveSharedConfigSubgroup(const PluginID & ID, const RegistryPath & group) -{ - bool result = GetSettings()->DeleteGroup(SharedGroup(ID, group)); - if (result) - { - GetSettings()->Flush(); - } - - return result; -} - -bool PluginManager::RemoveSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key) -{ - bool result = GetSettings()->DeleteEntry(SharedKey(ID, group, key)); - if (result) - { - GetSettings()->Flush(); - } - - return result; -} - -bool PluginManager::HasPrivateConfigGroup(const PluginID & ID, const RegistryPath & group) -{ - return HasGroup(PrivateGroup(ID, group)); -} - -bool PluginManager::GetPrivateConfigSubgroups(const PluginID & ID, const RegistryPath & group, RegistryPaths & subgroups) -{ - return GetSubgroups(PrivateGroup(ID, group), subgroups); -} - -bool PluginManager::GetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, wxString & value, const wxString & defval) -{ - return GetConfig(PrivateKey(ID, group, key), value, defval); -} - -bool PluginManager::GetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, int & value, int defval) -{ - return GetConfig(PrivateKey(ID, group, key), value, defval); -} - -bool PluginManager::GetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, bool & value, bool defval) -{ - return GetConfig(PrivateKey(ID, group, key), value, defval); -} - -bool PluginManager::GetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, float & value, float defval) -{ - return GetConfig(PrivateKey(ID, group, key), value, defval); -} - -bool PluginManager::GetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, double & value, double defval) -{ - return GetConfig(PrivateKey(ID, group, key), value, defval); -} - -bool PluginManager::SetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const wxString & value) -{ - return SetConfig(PrivateKey(ID, group, key), value); -} - -bool PluginManager::SetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const int & value) -{ - return SetConfig(PrivateKey(ID, group, key), value); -} - -bool PluginManager::SetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const bool & value) -{ - return SetConfig(PrivateKey(ID, group, key), value); -} - -bool PluginManager::SetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const float & value) -{ - return SetConfig(PrivateKey(ID, group, key), value); -} - -bool PluginManager::SetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const double & value) -{ - return SetConfig(PrivateKey(ID, group, key), value); -} - -bool PluginManager::RemovePrivateConfigSubgroup(const PluginID & ID, const RegistryPath & group) -{ - bool result = GetSettings()->DeleteGroup(PrivateGroup(ID, group)); - if (result) - { - GetSettings()->Flush(); - } - - return result; -} - -bool PluginManager::RemovePrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key) -{ - bool result = GetSettings()->DeleteEntry(PrivateKey(ID, group, key)); - if (result) - { - GetSettings()->Flush(); - } - - return result; -} - -// ============================================================================ -// -// PluginManager -// -// ============================================================================ - -// The one and only PluginManager -std::unique_ptr PluginManager::mInstance{}; - -// ---------------------------------------------------------------------------- -// Creation/Destruction -// ---------------------------------------------------------------------------- - -PluginManager::PluginManager() -{ - mSettings = NULL; -} - -PluginManager::~PluginManager() -{ - // Ensure termination (harmless if already done) - Terminate(); -} - -// ---------------------------------------------------------------------------- -// PluginManager implementation -// ---------------------------------------------------------------------------- - -// ============================================================================ -// -// Return reference to singleton -// -// (Thread-safe...no active threading during construction or after destruction) -// ============================================================================ - -PluginManager & PluginManager::Get() -{ - if (!mInstance) - { - mInstance.reset(safenew PluginManager); - } - - return *mInstance; -} - -void PluginManager::Initialize() -{ - // Always load the registry first - Load(); - - // And force load of setting to verify it's accessible - GetSettings(); - - // Then look for providers (they may autoregister plugins) - ModuleManager::Get().DiscoverProviders(); - - // And finally check for updates -#ifndef EXPERIMENTAL_EFFECT_MANAGEMENT - CheckForUpdates(); -#else - const bool kFast = true; - CheckForUpdates( kFast ); -#endif -} - -void PluginManager::Terminate() -{ - // Get rid of all non-module plugins first - PluginMap::iterator iter = mPlugins.begin(); - while (iter != mPlugins.end()) - { - PluginDescriptor & plug = iter->second; - if (plug.GetPluginType() == PluginTypeEffect) - { - mPlugins.erase(iter++); - continue; - } - - ++iter; - } - - // Now get rid of the modules - iter = mPlugins.begin(); - while (iter != mPlugins.end()) - { - mPlugins.erase(iter++); - } -} - -bool PluginManager::DropFile(const wxString &fileName) -{ - auto &mm = ModuleManager::Get(); - const wxFileName src{ fileName }; - - for (auto &plug : PluginsOfType(PluginTypeModule)) { - auto module = static_cast - (mm.CreateProviderInstance(plug.GetID(), plug.GetPath())); - if (! module) - continue; - - const auto &ff = module->InstallPath(); - const auto &extensions = module->GetFileExtensions(); - if ( !ff.empty() && - extensions.Index(src.GetExt(), false) != wxNOT_FOUND ) { - TranslatableString errMsg; - // Do dry-run test of the file format - unsigned nPlugIns = - module->DiscoverPluginsAtPath(fileName, errMsg, {}); - if (nPlugIns) { - // File contents are good for this module, so check no others. - // All branches of this block return true, even in case of - // failure for other reasons, to signal that other drag-and-drop - // actions should not be tried. - - // Find path to copy it - wxFileName dst; - dst.AssignDir( ff ); - dst.SetFullName( src.GetFullName() ); - if ( dst.Exists() ) { - // Query whether to overwrite - bool overwrite = (wxYES == ::AudacityMessageBox( - XO("Overwrite the plug-in file %s?") - .Format( dst.GetFullPath() ), - XO("Plug-in already exists"), - wxYES_NO ) ); - if ( !overwrite ) - return true; - } - - // Move the file or subtree - bool copied = false; - auto dstPath = dst.GetFullPath(); - if ( src.FileExists() ) - // A simple one-file plug-in - copied = FileNames::DoCopyFile( - src.GetFullPath(), dstPath, true ); - else { - // A sub-folder - // such as for some VST packages - // Recursive copy needed -- to do - return true; - } - - if (!copied) { - ::AudacityMessageBox( - XO("Plug-in file is in use. Failed to overwrite") ); - return true; - } - - // Register for real - std::vector ids; - std::vector names; - nPlugIns = module->DiscoverPluginsAtPath(dstPath, errMsg, - [&](ModuleInterface *provider, ComponentInterface *ident) - -> const PluginID& { - // Register as by default, but also collecting the PluginIDs - // and names - auto &id = PluginManagerInterface::DefaultRegistrationCallback( - provider, ident); - ids.push_back(id); - names.push_back( ident->GetSymbol().Translation() ); - return id; - }); - if ( ! nPlugIns ) { - // Unlikely after the dry run succeeded - ::AudacityMessageBox( - XO("Failed to register:\n%s").Format( errMsg ) ); - return true; - } - - // Ask whether to enable the plug-ins - if (auto nIds = ids.size()) { - auto message = XPC( - /* i18n-hint A plug-in is an optional added program for a sound - effect, or generator, or analyzer */ - "Enable this plug-in?\n", - "Enable these plug-ins?\n", - 0, - "plug-ins" - )( nIds ); - for (const auto &name : names) - message.Join( Verbatim( name ), wxT("\n") ); - bool enable = (wxYES == ::AudacityMessageBox( - message, - XO("Enable new plug-ins"), - wxYES_NO ) ); - for (const auto &id : ids) - mPlugins[id].SetEnabled(enable); - // Make changes to enabled status persist: - this->Save(); - } - - return true; - } - } - } - - return false; -} - -void PluginManager::Load() -{ - // Create/Open the registry - auto pRegistry = AudacityFileConfig::Create( - {}, {}, FileNames::PluginRegistry()); - auto ®istry = *pRegistry; - - // If this group doesn't exist then we have something that's not a registry. - // We should probably warn the user, but it's pretty unlikely that this will happen. - if (!registry.HasGroup(REGROOT)) - { - // Must start over - registry.DeleteAll(); - registry.Flush(); - return; - } - - // Check for a registry version that we can understand - // TODO: Should also check for a registry file that is newer than - // what we can understand. - wxString regver = registry.Read(REGVERKEY); - if (regver < REGVERCUR ) - { - // Conversion code here, for when registry version changes. - - // We iterate through the effects, possibly updating their info. - wxString groupName; - long groupIndex; - wxString group = GetPluginTypeString(PluginTypeEffect); - wxString cfgPath = REGROOT + group + wxCONFIG_PATH_SEPARATOR; - wxArrayString groupsToDelete; - - registry.SetPath(cfgPath); - for (bool cont = registry.GetFirstGroup(groupName, groupIndex); - cont; - registry.SetPath(cfgPath), - cont = registry.GetNextGroup(groupName, groupIndex)) - { - registry.SetPath(groupName); - wxString effectSymbol = registry.Read(KEY_SYMBOL, ""); - wxString effectVersion = registry.Read(KEY_VERSION, ""); - - - // For 2.3.0 the plugins we distribute have moved around. - // So we upped the registry version number to 1.1. - // These particular config edits were originally written to fix Bug 1914. - if (regver <= "1.0") { - // Nyquist prompt is a built-in that has moved to the tools menu. - if (effectSymbol == NYQUIST_PROMPT_ID) { - registry.Write(KEY_EFFECTTYPE, "Tool"); - // Old version of SDE was in Analyze menu. Now it is in Tools. - // We don't want both the old and the new. - } else if ((effectSymbol == "Sample Data Export") && (effectVersion == "n/a")) { - groupsToDelete.push_back(cfgPath + groupName); - // Old version of SDI was in Generate menu. Now it is in Tools. - } else if ((effectSymbol == "Sample Data Import") && (effectVersion == "n/a")) { - groupsToDelete.push_back(cfgPath + groupName); - } - } - - } - // Doing the deletion within the search loop risked skipping some items, - // hence the delayed delete. - for (unsigned int i = 0; i < groupsToDelete.size(); i++) { - registry.DeleteGroup(groupsToDelete[i]); - } - registry.SetPath(""); - registry.Write(REGVERKEY, REGVERCUR); - // Updates done. Make sure we read the updated data later. - registry.Flush(); - } - - // Load all provider plugins first - LoadGroup(®istry, PluginTypeModule); - - // Now the rest - LoadGroup(®istry, PluginTypeEffect); - LoadGroup(®istry, PluginTypeAudacityCommand ); - LoadGroup(®istry, PluginTypeExporter); - LoadGroup(®istry, PluginTypeImporter); - - LoadGroup(®istry, PluginTypeStub); - return; -} - -void PluginManager::LoadGroup(FileConfig *pRegistry, PluginType type) -{ -#ifdef __WXMAC__ - // Bug 1590: On Mac, we should purge the registry of Nyquist plug-ins - // bundled with other versions of Audacity, assuming both versions - // were properly installed in /Applications (or whatever it is called in - // your locale) - - const auto fullExePath = PlatformCompatibility::GetExecutablePath(); - - // Strip rightmost path components up to *.app - wxFileName exeFn{ fullExePath }; - exeFn.SetEmptyExt(); - exeFn.SetName(wxString{}); - while(exeFn.GetDirCount() && !exeFn.GetDirs().back().EndsWith(".app")) - exeFn.RemoveLastDir(); - - const auto goodPath = exeFn.GetPath(); - - if(exeFn.GetDirCount()) - exeFn.RemoveLastDir(); - const auto possiblyBadPath = exeFn.GetPath(); - - auto AcceptPath = [&](const wxString &path) { - if (!path.StartsWith(possiblyBadPath)) - // Assume it's not under /Applications - return true; - if (path.StartsWith(goodPath)) - // It's bundled with this executable - return true; - return false; - }; -#else - auto AcceptPath = [](const wxString&){ return true; }; -#endif - - wxString strVal; - bool boolVal; - wxString groupName; - long groupIndex; - wxString group = GetPluginTypeString(type); - wxString cfgPath = REGROOT + group + wxCONFIG_PATH_SEPARATOR; - - pRegistry->SetPath(cfgPath); - for (bool cont = pRegistry->GetFirstGroup(groupName, groupIndex); - cont; - pRegistry->SetPath(cfgPath), - cont = pRegistry->GetNextGroup(groupName, groupIndex)) - { - PluginDescriptor plug; - - pRegistry->SetPath(groupName); - - groupName = ConvertID(groupName); - - // Bypass group if the ID is already in use - if (mPlugins.count(groupName)) - continue; - - // Set the ID and type - plug.SetID(groupName); - plug.SetPluginType(type); - - // Get the provider ID and bypass group if not found - if (!pRegistry->Read(KEY_PROVIDERID, &strVal, wxEmptyString)) - { - // Bypass group if the provider isn't valid - if (!strVal.empty() && !mPlugins.count(strVal)) - continue; - } - plug.SetProviderID(PluginID(strVal)); - - // Get the path (optional) - pRegistry->Read(KEY_PATH, &strVal, wxEmptyString); - if (!AcceptPath(strVal)) - // Ignore the obsolete path in the config file, during session, - // but don't remove it from the file. Maybe you really want to - // switch back to the other version of Audacity and lose nothing. - continue; - plug.SetPath(strVal); - - /* - // PRL: Ignore names written in configs before 2.3.0! - // use Internal string only! Let the present version of Audacity map - // that to a user-visible string. - // Get the name and bypass group if not found - if (!pRegistry->Read(KEY_NAME, &strVal)) - { - continue; - } - plug.SetName(strVal); - */ - - // Get the symbol...Audacity 2.3.0 or later requires it - // bypass group if not found - // Note, KEY_SYMBOL started getting written to config files in 2.1.0. - // KEY_NAME (now ignored) was written before that, but only for VST - // effects. - if (!pRegistry->Read(KEY_SYMBOL, &strVal)) - continue; - - // Related to Bug2778: config file only remembered an internal name, - // so this symbol may not contain the correct TranslatableString. - // See calls to IsPluginRegistered which can correct that. - plug.SetSymbol(strVal); - - // Get the version and bypass group if not found - if (!pRegistry->Read(KEY_VERSION, &strVal)) - { - continue; - } - plug.SetVersion(strVal); - - // Get the vendor and bypass group if not found - if (!pRegistry->Read(KEY_VENDOR, &strVal)) - { - continue; - } - plug.SetVendor( strVal ); - -#if 0 - // This was done before version 2.2.2, but the value was not really used - // But absence of a value will cause early versions to skip the group - // Therefore we still write a blank to keep pluginregistry.cfg - // backwards-compatible - - // Get the description and bypass group if not found - if (!pRegistry->Read(KEY_DESCRIPTION, &strVal)) - { - continue; - } -#endif - - // Is it enabled...default to no if not found - pRegistry->Read(KEY_ENABLED, &boolVal, false); - plug.SetEnabled(boolVal); - - // Is it valid...default to no if not found - pRegistry->Read(KEY_VALID, &boolVal, false); - plug.SetValid(boolVal); - - switch (type) - { - case PluginTypeModule: - { - // Nothing to do here yet - } - break; - - case PluginTypeEffect: - { - // Get the effect type and bypass group if not found - if (!pRegistry->Read(KEY_EFFECTTYPE, &strVal)) - continue; - - if (strVal == KEY_EFFECTTYPE_NONE) - plug.SetEffectType(EffectTypeNone); - else if (strVal == KEY_EFFECTTYPE_ANALYZE) - plug.SetEffectType(EffectTypeAnalyze); - else if (strVal == KEY_EFFECTTYPE_GENERATE) - plug.SetEffectType(EffectTypeGenerate); - else if (strVal == KEY_EFFECTTYPE_PROCESS) - plug.SetEffectType(EffectTypeProcess); - else if (strVal == KEY_EFFECTTYPE_TOOL) - plug.SetEffectType(EffectTypeTool); - else if (strVal == KEY_EFFECTTYPE_HIDDEN) - plug.SetEffectType(EffectTypeHidden); - else - continue; - - // Get the effect family and bypass group if not found - if (!pRegistry->Read(KEY_EFFECTFAMILY, &strVal)) - { - continue; - } - plug.SetEffectFamily(strVal); - - // Is it a default (above the line) effect and bypass group if not found - if (!pRegistry->Read(KEY_EFFECTDEFAULT, &boolVal)) - { - continue; - } - plug.SetEffectDefault(boolVal); - - // Is it an interactive effect and bypass group if not found - if (!pRegistry->Read(KEY_EFFECTINTERACTIVE, &boolVal)) - { - continue; - } - plug.SetEffectInteractive(boolVal); - - // Is it a realtime capable effect and bypass group if not found - if (!pRegistry->Read(KEY_EFFECTREALTIME, &boolVal)) - { - continue; - } - plug.SetEffectRealtime(boolVal); - - // Does the effect support automation...bypass group if not found - if (!pRegistry->Read(KEY_EFFECTAUTOMATABLE, &boolVal)) - { - continue; - } - plug.SetEffectAutomatable(boolVal); - } - break; - - case PluginTypeImporter: - { - // Get the importer identifier and bypass group if not found - if (!pRegistry->Read(KEY_IMPORTERIDENT, &strVal)) - { - continue; - } - plug.SetImporterIdentifier(strVal); - - // Get the importer extensions and bypass group if not found - if (!pRegistry->Read(KEY_IMPORTEREXTENSIONS, &strVal)) - { - continue; - } - FileExtensions extensions; - wxStringTokenizer tkr(strVal, wxT(":")); - while (tkr.HasMoreTokens()) - { - extensions.push_back(tkr.GetNextToken()); - } - plug.SetImporterExtensions(extensions); - } - break; - - case PluginTypeStub: - { - // Nothing additional for stubs - } - break; - - // Not used by 2.1.1 or greater and should be removed after a few releases past 2.1.0. - case PluginTypeNone: - { - // Used for stub groups - } - break; - - default: - { - continue; - } - } - - // Everything checked out...accept the plugin - mPlugins[groupName] = std::move(plug); - } - - return; -} - -void PluginManager::Save() -{ - // Create/Open the registry - auto pRegistry = AudacityFileConfig::Create( - {}, {}, FileNames::PluginRegistry()); - auto ®istry = *pRegistry; - - // Clear it out - registry.DeleteAll(); - - // Write the version string - registry.Write(REGVERKEY, REGVERCUR); - - // Save the individual groups - SaveGroup(®istry, PluginTypeEffect); - SaveGroup(®istry, PluginTypeExporter); - SaveGroup(®istry, PluginTypeAudacityCommand); - SaveGroup(®istry, PluginTypeImporter); - SaveGroup(®istry, PluginTypeStub); - - // Not used by 2.1.1 or greater, but must save to allow users to switch between 2.1.0 - // and 2.1.1+. This should be removed after a few releases past 2.1.0. - //SaveGroup(®istry, PluginTypeNone); - - // And now the providers - SaveGroup(®istry, PluginTypeModule); - - // Just to be safe - registry.Flush(); -} - -void PluginManager::SaveGroup(FileConfig *pRegistry, PluginType type) -{ - wxString group = GetPluginTypeString(type); - for (auto &pair : mPlugins) { - auto & plug = pair.second; - - if (plug.GetPluginType() != type) - { - continue; - } - - pRegistry->SetPath(REGROOT + group + wxCONFIG_PATH_SEPARATOR + ConvertID(plug.GetID())); - - pRegistry->Write(KEY_PATH, plug.GetPath()); - - // See comments with the corresponding load-time call to SetSymbol(). - pRegistry->Write(KEY_SYMBOL, plug.GetSymbol().Internal()); - - // PRL: Writing KEY_NAME which is no longer read, but older Audacity - // versions expect to find it. - pRegistry->Write(KEY_NAME, plug.GetSymbol().Msgid().MSGID()); - - pRegistry->Write(KEY_VERSION, plug.GetUntranslatedVersion()); - pRegistry->Write(KEY_VENDOR, plug.GetVendor()); - // Write a blank -- see comments in LoadGroup: - pRegistry->Write(KEY_DESCRIPTION, wxString{}); - pRegistry->Write(KEY_PROVIDERID, plug.GetProviderID()); - pRegistry->Write(KEY_ENABLED, plug.IsEnabled()); - pRegistry->Write(KEY_VALID, plug.IsValid()); - - switch (type) - { - case PluginTypeModule: - break; - - case PluginTypeEffect: - { - EffectType etype = plug.GetEffectType(); - wxString stype; - if (etype == EffectTypeNone) - stype = KEY_EFFECTTYPE_NONE; - else if (etype == EffectTypeAnalyze) - stype = KEY_EFFECTTYPE_ANALYZE; - else if (etype == EffectTypeGenerate) - stype = KEY_EFFECTTYPE_GENERATE; - else if (etype == EffectTypeProcess) - stype = KEY_EFFECTTYPE_PROCESS; - else if (etype == EffectTypeTool) - stype = KEY_EFFECTTYPE_TOOL; - else if (etype == EffectTypeHidden) - stype = KEY_EFFECTTYPE_HIDDEN; - - pRegistry->Write(KEY_EFFECTTYPE, stype); - pRegistry->Write(KEY_EFFECTFAMILY, plug.GetEffectFamily()); - pRegistry->Write(KEY_EFFECTDEFAULT, plug.IsEffectDefault()); - pRegistry->Write(KEY_EFFECTINTERACTIVE, plug.IsEffectInteractive()); - pRegistry->Write(KEY_EFFECTREALTIME, plug.IsEffectRealtime()); - pRegistry->Write(KEY_EFFECTAUTOMATABLE, plug.IsEffectAutomatable()); - } - break; - - case PluginTypeImporter: - { - pRegistry->Write(KEY_IMPORTERIDENT, plug.GetImporterIdentifier()); - const auto & extensions = plug.GetImporterExtensions(); - wxString strExt; - for (size_t i = 0, cnt = extensions.size(); i < cnt; i++) - { - strExt += extensions[i] + wxT(":"); - } - strExt.RemoveLast(1); - pRegistry->Write(KEY_IMPORTEREXTENSIONS, strExt); - } - break; - - default: - break; - } - } - - return; -} - -// If bFast is true, do not do a full check. Just check the ones -// that are quick to check. Currently (Feb 2017) just Nyquist -// and built-ins. -void PluginManager::CheckForUpdates(bool bFast) -{ - ModuleManager & mm = ModuleManager::Get(); - wxArrayString pathIndex; - for (auto &pair : mPlugins) { - auto &plug = pair.second; - - // Bypass 2.1.0 placeholders...remove this after a few releases past 2.1.0 - if (plug.GetPluginType() != PluginTypeNone) - pathIndex.push_back(plug.GetPath().BeforeFirst(wxT(';'))); - } - - // Check all known plugins to ensure they are still valid and scan for NEW ones. - // - // All NEW plugins get a stub entry created that will remain in place until the - // user enables or disables the plugin. - // - // Because we use the plugins "path" as returned by the providers, we can actually - // have multiple providers report the same path since, at this point, they only - // know that the path might possibly be one supported by the provider. - // - // When the user enables the plugin, each provider that reported it will be asked - // to register the plugin. - for (auto &pair : mPlugins) { - auto &plug = pair.second; - const PluginID & plugID = plug.GetID(); - const wxString & plugPath = plug.GetPath(); - PluginType plugType = plug.GetPluginType(); - - // Bypass 2.1.0 placeholders...remove this after a few releases past 2.1.0 - if (plugType == PluginTypeNone) - { - continue; - } - - if ( plugType == PluginTypeModule ) - { - if( bFast ) - { - // Skip modules, when doing a fast refresh/check. - } - else if (!mm.IsProviderValid(plugID, plugPath)) - { - plug.SetEnabled(false); - plug.SetValid(false); - } - else - { - // Collect plugin paths - auto paths = mm.FindPluginsForProvider(plugID, plugPath); - for (size_t i = 0, cnt = paths.size(); i < cnt; i++) - { - wxString path = paths[i].BeforeFirst(wxT(';'));; - if ( ! make_iterator_range( pathIndex ).contains( path ) ) - { - PluginID ID = plugID + wxT("_") + path; - PluginDescriptor & plug2 = mPlugins[ID]; // This will create a NEW descriptor - plug2.SetPluginType(PluginTypeStub); - plug2.SetID(ID); - plug2.SetProviderID(plugID); - plug2.SetPath(path); - plug2.SetEnabled(false); - plug2.SetValid(false); - } - } - } - } - else if (plugType != PluginTypeNone && plugType != PluginTypeStub) - { - plug.SetValid(mm.IsPluginValid(plug.GetProviderID(), plugPath, bFast)); - if (!plug.IsValid()) - { - plug.SetEnabled(false); - } - } - } - - Save(); - - return; -} - -bool PluginManager::ShowManager(wxWindow *parent, EffectType type) -{ - CheckForUpdates(); - - PluginRegistrationDialog dlg(parent, type); - return dlg.ShowModal() == wxID_OK; -} - -// Here solely for the purpose of Nyquist Workbench until -// a better solution is devised. -const PluginID & PluginManager::RegisterPlugin( - std::unique_ptr effect, PluginType type) -{ - PluginDescriptor & plug = - CreatePlugin(GetID(effect.get()), effect.get(), type); - - plug.SetEffectType(effect->GetType()); - plug.SetEffectFamily(effect->GetFamily().Internal()); - plug.SetEffectInteractive(effect->IsInteractive()); - plug.SetEffectDefault(effect->IsDefault()); - plug.SetEffectRealtime(effect->SupportsRealtime()); - plug.SetEffectAutomatable(effect->SupportsAutomation()); - - plug.SetInstance(std::move(effect)); - plug.SetEffectLegacy(true); - plug.SetEnabled(true); - plug.SetValid(true); - - return plug.GetID(); -} - -void PluginManager::UnregisterPlugin(const PluginID & ID) -{ - mPlugins.erase(ID); -} - -int PluginManager::GetPluginCount(PluginType type) -{ - return count_if(mPlugins.begin(), mPlugins.end(), [type](auto &pair){ - return pair.second.GetPluginType() == type; }); -} - -const PluginDescriptor *PluginManager::GetPlugin(const PluginID & ID) const -{ - if (auto iter = mPlugins.find(ID); iter == mPlugins.end()) - return nullptr; - else - return &iter->second; -} - -void PluginManager::Iterator::Advance(bool incrementing) -{ - const auto end = mPm.mPlugins.end(); - if (incrementing && mIterator != end) - ++mIterator; - bool all = mPluginType == PluginTypeNone && mEffectType == EffectTypeNone; - for (; mIterator != end; ++mIterator) { - auto &plug = mIterator->second; - if (!all && !(plug.IsValid() && plug.IsEnabled())) - continue; - auto plugType = plug.GetPluginType(); - if ((mPluginType == PluginTypeNone || (plugType & mPluginType)) && - (mEffectType == EffectTypeNone || plug.GetEffectType() == mEffectType)) { - if (!all && (plugType & PluginTypeEffect)) { - // This preference may be written by EffectsPrefs - auto setting = mPm.GetPluginEnabledSetting( plug ); - if (!(setting.empty() || gPrefs->Read( setting, true ))) - continue; - } - // Pause iteration at this match - break; - } - } -} - -PluginManager::Iterator::Iterator(PluginManager &manager) -: mPm{ manager } -, mIterator{ manager.mPlugins.begin() } -{ -} - -PluginManager::Iterator::Iterator(PluginManager &manager, int type) -: mPm{ manager } -, mIterator{ manager.mPlugins.begin() } -, mPluginType{ type } -{ - Advance(false); -} - -PluginManager::Iterator::Iterator(PluginManager &manager, EffectType type) -: mPm{ manager } -, mIterator{ manager.mPlugins.begin() } -, mEffectType{ type } -{ - Advance(false); -} - -auto PluginManager::Iterator::operator ++() -> Iterator & -{ - Advance(true); - return *this; -} - -bool PluginManager::IsPluginEnabled(const PluginID & ID) -{ - if (auto iter = mPlugins.find(ID); iter == mPlugins.end()) - return false; - else - return iter->second.IsEnabled(); -} - -void PluginManager::EnablePlugin(const PluginID & ID, bool enable) -{ - if (auto iter = mPlugins.find(ID); iter == mPlugins.end()) - return; - else - iter->second.SetEnabled(enable); -} - -const ComponentInterfaceSymbol & PluginManager::GetSymbol(const PluginID & ID) -{ - if (auto iter = mPlugins.find(ID); iter == mPlugins.end()) { - static ComponentInterfaceSymbol empty; - return empty; - } - else - return iter->second.GetSymbol(); -} - -ComponentInterface *PluginManager::GetInstance(const PluginID & ID) -{ - if (auto iter = mPlugins.find(ID); iter == mPlugins.end()) - return nullptr; - else { - auto &plug = iter->second; - - // If not dealing with legacy effects, make sure the provider is loaded - if (!plug.IsEffectLegacy()) - { - const PluginID & prov = plug.GetProviderID(); - if (auto iter2 = mPlugins.find(prov); iter2 == mPlugins.end()) - return nullptr; - else - iter2->second.GetInstance(); - } - - return plug.GetInstance(); - } -} - -PluginID PluginManager::GetID(ModuleInterface *module) -{ - return wxString::Format(wxT("%s_%s_%s_%s_%s"), - GetPluginTypeString(PluginTypeModule), - wxEmptyString, - module->GetVendor().Internal(), - module->GetSymbol().Internal(), - module->GetPath()); -} - -PluginID PluginManager::GetID(ComponentInterface *command) -{ - return wxString::Format(wxT("%s_%s_%s_%s_%s"), - GetPluginTypeString(PluginTypeAudacityCommand), - wxEmptyString, - command->GetVendor().Internal(), - command->GetSymbol().Internal(), - command->GetPath()); -} - -PluginID PluginManager::GetID(EffectDefinitionInterface *effect) -{ - return wxString::Format(wxT("%s_%s_%s_%s_%s"), - GetPluginTypeString(PluginTypeEffect), - effect->GetFamily().Internal(), - effect->GetVendor().Internal(), - effect->GetSymbol().Internal(), - effect->GetPath()); -} - -PluginID PluginManager::GetID(ImporterInterface *importer) -{ - return wxString::Format(wxT("%s_%s_%s_%s_%s"), - GetPluginTypeString(PluginTypeImporter), - wxEmptyString, - importer->GetVendor().Internal(), - importer->GetSymbol().Internal(), - importer->GetPath()); -} - -// This string persists in configuration files -// So config compatibility will break if it is changed across Audacity versions -wxString PluginManager::GetPluginTypeString(PluginType type) -{ - wxString str; - - switch (type) - { - default: - case PluginTypeNone: - str = wxT("Placeholder"); - break; - case PluginTypeStub: - str = wxT("Stub"); - break; - case PluginTypeEffect: - str = wxT("Effect"); - break; - case PluginTypeAudacityCommand: - str = wxT("Generic"); - break; - case PluginTypeExporter: - str = wxT("Exporter"); - break; - case PluginTypeImporter: - str = wxT("Importer"); - break; - case PluginTypeModule: - str = wxT("Module"); - break; - } - - return str; -} - -PluginDescriptor & PluginManager::CreatePlugin(const PluginID & id, - ComponentInterface *ident, - PluginType type) -{ - // This will either create a NEW entry or replace an existing entry - PluginDescriptor & plug = mPlugins[id]; - - plug.SetPluginType(type); - - plug.SetID(id); - plug.SetPath(ident->GetPath()); - plug.SetSymbol(ident->GetSymbol()); - plug.SetVendor(ident->GetVendor().Internal()); - plug.SetVersion(ident->GetVersion()); - - return plug; -} - -FileConfig *PluginManager::GetSettings() -{ - if (!mSettings) - { - mSettings = - AudacityFileConfig::Create({}, {}, FileNames::PluginSettings()); - - // Check for a settings version that we can understand - if (mSettings->HasEntry(SETVERKEY)) - { - wxString setver = mSettings->Read(SETVERKEY, SETVERKEY); - if (setver < SETVERCUR ) - { - // This is where we'd put in conversion code when the - // settings version changes. - // - // Should also check for a settings file that is newer than - // what we can understand. - } - } - else - { - // Make sure is has a version string - mSettings->Write(SETVERKEY, SETVERCUR); - mSettings->Flush(); - } - } - - return mSettings.get(); -} - -bool PluginManager::HasGroup(const RegistryPath & group) -{ - auto settings = GetSettings(); - - bool res = settings->HasGroup(group); - if (res) - { - // The group exists, but empty groups aren't considered valid - wxString oldPath = settings->GetPath(); - settings->SetPath(group); - res = settings->GetNumberOfEntries() || settings->GetNumberOfGroups(); - settings->SetPath(oldPath); - } - - return res; -} - -bool PluginManager::GetSubgroups(const RegistryPath & group, RegistryPaths & subgroups) -{ - if (group.empty() || !HasGroup(group)) - { - return false; - } - - wxString path = GetSettings()->GetPath(); - GetSettings()->SetPath(group); - - wxString name; - long index = 0; - if (GetSettings()->GetFirstGroup(name, index)) - { - do - { - subgroups.push_back(name); - } while (GetSettings()->GetNextGroup(name, index)); - } - - GetSettings()->SetPath(path); - - return true; -} - -bool PluginManager::GetConfig(const RegistryPath & key, int & value, int defval) -{ - bool result = false; - - if (!key.empty()) - { - result = GetSettings()->Read(key, &value, defval); - } - - return result; -} - -bool PluginManager::GetConfig(const RegistryPath & key, wxString & value, const wxString & defval) -{ - bool result = false; - - if (!key.empty()) - { - wxString wxval; - - result = GetSettings()->Read(key, &wxval, defval); - - value = wxval; - } - - return result; -} - -bool PluginManager::GetConfig(const RegistryPath & key, bool & value, bool defval) -{ - bool result = false; - - if (!key.empty()) - { - result = GetSettings()->Read(key, &value, defval); - } - - return result; -} - -bool PluginManager::GetConfig(const RegistryPath & key, float & value, float defval) -{ - bool result = false; - - if (!key.empty()) - { - double dval = 0.0; - - result = GetSettings()->Read(key, &dval, (double) defval); - - value = (float) dval; - } - - return result; -} - -bool PluginManager::GetConfig(const RegistryPath & key, double & value, double defval) -{ - bool result = false; - - if (!key.empty()) - { - result = GetSettings()->Read(key, &value, defval); - } - - return result; -} - -bool PluginManager::SetConfig(const RegistryPath & key, const wxString & value) -{ - bool result = false; - - if (!key.empty()) - { - wxString wxval = value; - result = GetSettings()->Write(key, wxval); - if (result) - { - result = GetSettings()->Flush(); - } - } - - return result; -} - -bool PluginManager::SetConfig(const RegistryPath & key, const int & value) -{ - bool result = false; - - if (!key.empty()) - { - result = GetSettings()->Write(key, value); - if (result) - { - result = GetSettings()->Flush(); - } - } - - return result; -} - -bool PluginManager::SetConfig(const RegistryPath & key, const bool & value) -{ - bool result = false; - - if (!key.empty()) - { - result = GetSettings()->Write(key, value); - if (result) - { - result = GetSettings()->Flush(); - } - } - - return result; -} - -bool PluginManager::SetConfig(const RegistryPath & key, const float & value) -{ - bool result = false; - - if (!key.empty()) - { - result = GetSettings()->Write(key, value); - if (result) - { - result = GetSettings()->Flush(); - } - } - - return result; -} - -bool PluginManager::SetConfig(const RegistryPath & key, const double & value) -{ - bool result = false; - - if (!key.empty()) - { - result = GetSettings()->Write(key, value); - if (result) - { - result = GetSettings()->Flush(); - } - } - - return result; -} - -/* Return value is a key for lookup in a config file */ -RegistryPath PluginManager::SettingsPath(const PluginID & ID, bool shared) -{ - // All the strings reported by PluginDescriptor and used in this function - // persist in the plugin settings configuration file, so they should not - // be changed across Audacity versions, or else compatibility of the - // configuration files will break. - - if (auto iter = mPlugins.find(ID); iter == mPlugins.end()) - return {}; - else { - const PluginDescriptor & plug = iter->second; - - wxString id = GetPluginTypeString(plug.GetPluginType()) + - wxT("_") + - plug.GetEffectFamily() + // is empty for non-Effects - wxT("_") + - plug.GetVendor() + - wxT("_") + - (shared ? wxString{} : plug.GetSymbol().Internal()); - - return SETROOT + - ConvertID(id) + - wxCONFIG_PATH_SEPARATOR + - (shared ? wxT("shared") : wxT("private")) + - wxCONFIG_PATH_SEPARATOR; - } -} - -/* Return value is a key for lookup in a config file */ -RegistryPath PluginManager::SharedGroup(const PluginID & ID, const RegistryPath & group) -{ - wxString path = SettingsPath(ID, true); - - wxFileName ff(group); - if (!ff.GetName().empty()) - { - path += ff.GetFullPath(wxPATH_UNIX) + wxCONFIG_PATH_SEPARATOR; - } - - return path; -} - -/* Return value is a key for lookup in a config file */ -RegistryPath PluginManager::SharedKey(const PluginID & ID, const RegistryPath & group, const RegistryPath & key) -{ - auto path = SharedGroup(ID, group); - if (path.empty()) - { - return path; - } - - return path + key; -} - -/* Return value is a key for lookup in a config file */ -RegistryPath PluginManager::PrivateGroup(const PluginID & ID, const RegistryPath & group) -{ - auto path = SettingsPath(ID, false); - - wxFileName ff(group); - if (!ff.GetName().empty()) - { - path += ff.GetFullPath(wxPATH_UNIX) + wxCONFIG_PATH_SEPARATOR; - } - - return path; -} - -/* Return value is a key for lookup in a config file */ -RegistryPath PluginManager::PrivateKey(const PluginID & ID, const RegistryPath & group, const RegistryPath & key) -{ - auto path = PrivateGroup(ID, group); - if (path.empty()) - { - return path; - } - - return path + key; -} - -// Sanitize the ID...not the best solution, but will suffice until this -// is converted to XML. We use base64 encoding to preserve case. -wxString PluginManager::ConvertID(const PluginID & ID) -{ - if (ID.StartsWith(wxT("base64:"))) - { - wxString id = ID.Mid(7); - ArrayOf buf{ id.length() / 4 * 3 }; - id = wxString::FromUTF8(buf.get(), b64decode(id, buf.get())); - return id; - } - - const wxCharBuffer & buf = ID.ToUTF8(); - return wxT("base64:") + b64encode(buf, strlen(buf)); -} - -//////////////////////////////////////////////////////////////////////////////// -// Base64 en/decoding -// -// Original routines marked as public domain and found at: -// -// http://en.wikibooks.org/wiki/Algorithm_implementation/Miscellaneous/Base64 -// -//////////////////////////////////////////////////////////////////////////////// - -// Lookup table for encoding -const static wxChar cset[] = wxT("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"); -const static char padc = wxT('='); - -wxString PluginManager::b64encode(const void *in, int len) -{ - unsigned char *p = (unsigned char *) in; - wxString out; - - unsigned long temp; - for (int i = 0; i < len / 3; i++) - { - temp = (*p++) << 16; //Convert to big endian - temp += (*p++) << 8; - temp += (*p++); - out += cset[(temp & 0x00FC0000) >> 18]; - out += cset[(temp & 0x0003F000) >> 12]; - out += cset[(temp & 0x00000FC0) >> 6]; - out += cset[(temp & 0x0000003F)]; - } - - switch (len % 3) - { - case 1: - temp = (*p++) << 16; //Convert to big endian - out += cset[(temp & 0x00FC0000) >> 18]; - out += cset[(temp & 0x0003F000) >> 12]; - out += padc; - out += padc; - break; - - case 2: - temp = (*p++) << 16; //Convert to big endian - temp += (*p++) << 8; - out += cset[(temp & 0x00FC0000) >> 18]; - out += cset[(temp & 0x0003F000) >> 12]; - out += cset[(temp & 0x00000FC0) >> 6]; - out += padc; - break; - } - - return out; -} - -int PluginManager::b64decode(const wxString &in, void *out) -{ - int len = in.length(); - unsigned char *p = (unsigned char *) out; - - if (len % 4) //Sanity check - { - return 0; - } - - int padding = 0; - if (len) - { - if (in[len - 1] == padc) - { - padding++; - } - - if (in[len - 2] == padc) - { - padding++; - } - } - - //const char *a = in.mb_str(); - //Setup a vector to hold the result - unsigned long temp = 0; //Holds decoded quanta - int i = 0; - while (i < len) - { - for (int quantumPosition = 0; quantumPosition < 4; quantumPosition++) - { - unsigned char c = in[i]; - temp <<= 6; - - if (c >= 0x41 && c <= 0x5A) - { - temp |= c - 0x41; - } - else if (c >= 0x61 && c <= 0x7A) - { - temp |= c - 0x47; - } - else if (c >= 0x30 && c <= 0x39) - { - temp |= c + 0x04; - } - else if (c == 0x2B) - { - temp |= 0x3E; - } - else if (c == 0x2F) - { - temp |= 0x3F; - } - else if (c == padc) - { - switch (len - i) - { - case 1: //One pad character - *p++ = (temp >> 16) & 0x000000FF; - *p++ = (temp >> 8) & 0x000000FF; - return p - (unsigned char *) out; - case 2: //Two pad characters - *p++ = (temp >> 10) & 0x000000FF; - return p - (unsigned char *) out; - } - } - i++; - } - *p++ = (temp >> 16) & 0x000000FF; - *p++ = (temp >> 8) & 0x000000FF; - *p++ = temp & 0x000000FF; - } - - return p - (unsigned char *) out; -} - -// This is defined out-of-line here, to keep ComponentInterface free of other -// #include directives. -TranslatableString ComponentInterface::GetName() -{ - return GetSymbol().Msgid(); -} From 50b384adae5befea8919459a9c887ebcd24d698c Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Mon, 1 Mar 2021 22:07:43 -0500 Subject: [PATCH 07/10] Separate files for PluginRegistrationDialog... ... so that PluginManager can avoid using GUI --- src/CMakeLists.txt | 1 + src/PluginManager.cpp | 981 +------------ src/PluginManager.h | 2 - src/PluginRegistrationDialog.cpp | 2191 +----------------------------- src/menus/PluginMenus.cpp | 13 +- 5 files changed, 26 insertions(+), 3162 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b0b88c6fe..c35143aef 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -202,6 +202,7 @@ list( APPEND SOURCES PlaybackSchedule.h PluginManager.cpp PluginManager.h + PluginRegistrationDialog.cpp PluginRegistrationDialog.h Prefs.cpp Prefs.h diff --git a/src/PluginManager.cpp b/src/PluginManager.cpp index 7a18da43c..aefe72277 100644 --- a/src/PluginManager.cpp +++ b/src/PluginManager.cpp @@ -25,987 +25,18 @@ for shared and private configs - which need to move out. #include -#include // for wxUSE_* macros -#include -#include -#include -#include -#include -#include #include -#include -#include #include -#include -#include -#include "audacity/EffectInterface.h" #include "audacity/ModuleInterface.h" #include "AudacityFileConfig.h" +#include "Internat.h" // for macro XO #include "FileNames.h" +#include "MemoryX.h" #include "ModuleManager.h" #include "PlatformCompatibility.h" -#include "PluginRegistrationDialog.h" -#include "Prefs.h" -#include "ShuttleGui.h" -#include "wxFileNameWrapper.h" #include "widgets/AudacityMessageBox.h" -#include "widgets/ProgressDialog.h" - -#include - -// ============================================================================ -// -// -// -// ============================================================================ -#if wxUSE_ACCESSIBILITY -#include "widgets/WindowAccessible.h" - -class CheckListAx final : public WindowAccessible -{ -public: - CheckListAx(wxListCtrl * window); - - virtual ~ CheckListAx(); - - // Retrieves the address of an IDispatch interface for the specified child. - // All objects must support this property. - wxAccStatus GetChild( int childId, wxAccessible **child ) override; - - // Gets the number of children. - wxAccStatus GetChildCount( int *childCount ) override; - - // Gets the default action for this object (0) or > 0 (the action for a child). - // Return wxACC_OK even if there is no action. actionName is the action, or the empty - // string if there is no action. - // The retrieved string describes the action that is performed on an object, - // not what the object does as a result. For example, a toolbar button that prints - // a document has a default action of "Press" rather than "Prints the current document." - wxAccStatus GetDefaultAction( int childId, wxString *actionName ) override; - - // Returns the description for this object or a child. - wxAccStatus GetDescription( int childId, wxString *description ) override; - - // Gets the window with the keyboard focus. - // If childId is 0 and child is NULL, no object in - // this subhierarchy has the focus. - // If this object has the focus, child should be 'this'. - wxAccStatus GetFocus( int *childId, wxAccessible **child ) override; - - // Returns help text for this object or a child, similar to tooltip text. - wxAccStatus GetHelpText( int childId, wxString *helpText ) override; - - // Returns the keyboard shortcut for this object or child. - // Return e.g. ALT+K - wxAccStatus GetKeyboardShortcut( int childId, wxString *shortcut ) override; - - // Returns the rectangle for this object (id = 0) or a child element (id > 0). - // rect is in screen coordinates. - wxAccStatus GetLocation( wxRect& rect, int elementId ) override; - - // Gets the name of the specified object. - wxAccStatus GetName( int childId, wxString *name ) override; - - // Returns a role constant. - wxAccStatus GetRole( int childId, wxAccRole *role ) override; - - // Gets a variant representing the selected children - // of this object. - // Acceptable values: - // - a null variant (IsNull() returns TRUE) - // - a list variant (GetType() == wxT("list")) - // - an integer representing the selected child element, - // or 0 if this object is selected (GetType() == wxT("long")) - // - a "void*" pointer to a wxAccessible child object - wxAccStatus GetSelections( wxVariant *selections ) override; - - // Returns a state constant. - wxAccStatus GetState( int childId, long* state ) override; - - // Returns a localized string representing the value for the object - // or child. - wxAccStatus GetValue( int childId, wxString *strValue ) override; - - void SetSelected( int item, bool focused = true ); - -private: - wxListCtrl *mParent; - int mLastId; -}; - -CheckListAx::CheckListAx( wxListCtrl * window ) -: WindowAccessible( window ) -{ - mParent = window; - mLastId = -1; -} - -CheckListAx::~CheckListAx() -{ -} - -void CheckListAx::SetSelected( int item, bool focused ) -{ - if (mLastId != -1) - { - NotifyEvent( wxACC_EVENT_OBJECT_SELECTIONREMOVE, - mParent, - wxOBJID_CLIENT, - mLastId ); - mLastId = -1; - } - - if (item != -1) - { - if (focused) - { - NotifyEvent( wxACC_EVENT_OBJECT_FOCUS, - mParent, - wxOBJID_CLIENT, - item + 1 ); - } - - NotifyEvent( wxACC_EVENT_OBJECT_SELECTION, - mParent, - wxOBJID_CLIENT, - item + 1 ); - - mLastId = item + 1; - } -} - -// Retrieves the address of an IDispatch interface for the specified child. -// All objects must support this property. -wxAccStatus CheckListAx::GetChild( int childId, wxAccessible** child ) -{ - if( childId == wxACC_SELF ) - { - *child = this; - } - else - { - *child = NULL; - } - - return wxACC_OK; -} - -// Gets the number of children. -wxAccStatus CheckListAx::GetChildCount( int *childCount ) -{ - *childCount = mParent->GetItemCount(); - - return wxACC_OK; -} - -// Gets the default action for this object (0) or > 0 (the action for a child). -// Return wxACC_OK even if there is no action. actionName is the action, or the empty -// string if there is no action. -// The retrieved string describes the action that is performed on an object, -// not what the object does as a result. For example, a toolbar button that prints -// a document has a default action of "Press" rather than "Prints the current document." -wxAccStatus CheckListAx::GetDefaultAction( int WXUNUSED(childId), wxString *actionName ) -{ - actionName->clear(); - - return wxACC_OK; -} - -// Returns the description for this object or a child. -wxAccStatus CheckListAx::GetDescription( int WXUNUSED(childId), wxString *description ) -{ - description->clear(); - - return wxACC_OK; -} - -// Gets the window with the keyboard focus. -// If childId is 0 and child is NULL, no object in -// this subhierarchy has the focus. -// If this object has the focus, child should be 'this'. -wxAccStatus CheckListAx::GetFocus( int *childId, wxAccessible **child ) -{ - *childId = 0; - *child = this; - - return wxACC_OK; -} - -// Returns help text for this object or a child, similar to tooltip text. -wxAccStatus CheckListAx::GetHelpText( int WXUNUSED(childId), wxString *helpText ) -{ - helpText->clear(); - - return wxACC_OK; -} - -// Returns the keyboard shortcut for this object or child. -// Return e.g. ALT+K -wxAccStatus CheckListAx::GetKeyboardShortcut( int WXUNUSED(childId), wxString *shortcut ) -{ - shortcut->clear(); - - return wxACC_OK; -} - -// Returns the rectangle for this object (id = 0) or a child element (id > 0). -// rect is in screen coordinates. -wxAccStatus CheckListAx::GetLocation( wxRect& rect, int elementId ) -{ - if( elementId == wxACC_SELF ) - { - rect = mParent->GetRect(); - rect.SetPosition( mParent->GetParent()->ClientToScreen( rect.GetPosition() ) ); - } - else - { - if( elementId <= mParent->GetItemCount() ) - { - mParent->GetItemRect( elementId - 1, rect, wxLIST_RECT_LABEL ); - rect.SetPosition( mParent->ClientToScreen( rect.GetPosition() ) ); - } - } - - return wxACC_OK; -} - -// Gets the name of the specified object. -wxAccStatus CheckListAx::GetName( int WXUNUSED(childId), wxString *name ) -{ - *name = mParent->GetName(); - - return wxACC_OK; -} - -// Returns a role constant. -wxAccStatus CheckListAx::GetRole( int childId, wxAccRole *role ) -{ - if( childId == wxACC_SELF ) - { - *role = wxROLE_SYSTEM_LIST; - } - else - { - *role = wxROLE_SYSTEM_LISTITEM; - } - - return wxACC_OK; -} - -// Gets a variant representing the selected children -// of this object. -// Acceptable values: -// - a null variant (IsNull() returns TRUE) -// - a list variant (GetType() == wxT("list")) -// - an integer representing the selected child element, -// or 0 if this object is selected (GetType() == wxT("long")) -// - a "void*" pointer to a wxAccessible child object -wxAccStatus CheckListAx::GetSelections( wxVariant * WXUNUSED(selections) ) -{ - return wxACC_NOT_IMPLEMENTED; -} - -// Returns a state constant. -wxAccStatus CheckListAx::GetState( int childId, long *pState ) -{ - int flag = wxACC_STATE_SYSTEM_FOCUSABLE; - - if( childId == wxACC_SELF ) - { - flag |= wxACC_STATE_SYSTEM_FOCUSED; - } - else - { - wxListItem item; - - item.SetId( childId - 1 ); - item.SetState( wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED ); - item.SetMask( wxLIST_MASK_STATE ); - - if( mParent->GetItem( item ) ) - { - flag |= wxACC_STATE_SYSTEM_SELECTABLE; - - long state = item.GetState(); - - if( state & wxLIST_STATE_FOCUSED ) - { - flag |= wxACC_STATE_SYSTEM_FOCUSED; - } - - if( state & wxLIST_STATE_SELECTED ) - { - flag |= wxACC_STATE_SYSTEM_SELECTED; - } - } - } - - *pState = flag; - - return wxACC_OK; -} - -// Returns a localized string representing the value for the object -// or child. -wxAccStatus CheckListAx::GetValue( int childId, wxString *strValue ) -{ - if( childId == 0 ) - { - return wxACC_OK; - } - else - { - *strValue = mParent->GetItemText( childId - 1 ); - } - - return wxACC_OK; -} - -#endif - -// ============================================================================ -// -// -// -// ============================================================================ - -enum -{ - STATE_Enabled, - STATE_Disabled, - STATE_New, - - STATE_COUNT -}; - -enum -{ - ID_ShowAll = 10000, - ID_ShowEnabled, - ID_ShowDisabled, - ID_ShowNew, - ID_List, - ID_ClearAll, - ID_SelectAll, - ID_Enable, - ID_Disable, -}; - -enum -{ - COL_Name, - COL_State, - COL_Path, - - COL_COUNT -}; - -BEGIN_EVENT_TABLE(PluginRegistrationDialog, wxDialogWrapper) - EVT_LIST_COL_CLICK(ID_List, PluginRegistrationDialog::OnSort) - EVT_BUTTON(wxID_OK, PluginRegistrationDialog::OnOK) - EVT_BUTTON(wxID_CANCEL, PluginRegistrationDialog::OnCancel) - EVT_BUTTON(ID_ClearAll, PluginRegistrationDialog::OnClearAll) - EVT_BUTTON(ID_SelectAll, PluginRegistrationDialog::OnSelectAll) - EVT_BUTTON(ID_Enable, PluginRegistrationDialog::OnEnable) - EVT_BUTTON(ID_Disable, PluginRegistrationDialog::OnDisable) - EVT_RADIOBUTTON(ID_ShowAll, PluginRegistrationDialog::OnChangedVisibility) - EVT_RADIOBUTTON(ID_ShowEnabled, PluginRegistrationDialog::OnChangedVisibility) - EVT_RADIOBUTTON(ID_ShowDisabled, PluginRegistrationDialog::OnChangedVisibility) - EVT_RADIOBUTTON(ID_ShowNew, PluginRegistrationDialog::OnChangedVisibility) -END_EVENT_TABLE() - -PluginRegistrationDialog::PluginRegistrationDialog(wxWindow *parent, EffectType type) -: wxDialogWrapper(parent, - wxID_ANY, - XO("Manage Plug-ins"), - wxDefaultPosition, wxDefaultSize, - wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) -{ - mType = type; - mEffects = NULL; - SetName(); - - mStates.resize(STATE_COUNT); - mStates[STATE_Enabled] = _("Enabled"); - mStates[STATE_Disabled] = _("Disabled"); - mStates[STATE_New] = _("New"); - - mSortColumn = COL_Name; - mSortDirection = 1; - - Populate(); - - DoSort( mSortColumn ); -} - -void PluginRegistrationDialog::Populate() -{ - //------------------------- Main section -------------------- - ShuttleGui S(this, eIsCreating); - PopulateOrExchange(S); - // ----------------------- End of main section -------------- -} - -/// Defines the dialog and does data exchange with it. -void PluginRegistrationDialog::PopulateOrExchange(ShuttleGui &S) -{ - S.StartVerticalLay(true); - { - /*i18n-hint: The dialog shows a list of plugins with check-boxes - beside each one.*/ -// S.StartStatic(XO("Effects"), true); - S.StartVerticalLay(); - { - S.StartHorizontalLay(wxEXPAND, 0); - { - S.StartHorizontalLay(wxALIGN_LEFT, 0); - { - S.AddPrompt(XXO("Select effects, click the Enable or Disable button, then click OK.")); - } - S.EndHorizontalLay(); - - S.StartHorizontalLay(wxCENTER, 1); - { - S.AddSpace(1); - } - S.EndHorizontalLay(); - - S.StartHorizontalLay(wxALIGN_NOT | wxALIGN_LEFT, 0); - { - wxRadioButton *rb; - - /* i18n-hint: This is before radio buttons selecting which effects to show */ - S.AddPrompt(XXO("Show:")); - rb = S.Id(ID_ShowAll) - /* i18n-hint: Radio button to show all effects */ - .Name(XO("Show all")) - /* i18n-hint: Radio button to show all effects */ - .AddRadioButton(XXO("&All")); -#if wxUSE_ACCESSIBILITY - // so that name can be set on a standard control - rb->SetAccessible(safenew WindowAccessible(rb)); -#endif - - rb = S.Id(ID_ShowDisabled) - /* i18n-hint: Radio button to show just the currently disabled effects */ - .Name(XO("Show disabled")) - /* i18n-hint: Radio button to show just the currently disabled effects */ - .AddRadioButtonToGroup(XXO("D&isabled")); -#if wxUSE_ACCESSIBILITY - // so that name can be set on a standard control - rb->SetAccessible(safenew WindowAccessible(rb)); -#endif - - rb = S.Id(ID_ShowEnabled) - /* i18n-hint: Radio button to show just the currently enabled effects */ - .Name(XO("Show enabled")) - /* i18n-hint: Radio button to show just the currently enabled effects */ - .AddRadioButtonToGroup(XXO("E&nabled")); -#if wxUSE_ACCESSIBILITY - // so that name can be set on a standard control - rb->SetAccessible(safenew WindowAccessible(rb)); -#endif - - rb = S.Id(ID_ShowNew) - /* i18n-hint: Radio button to show just the newly discovered effects */ - .Name(XO("Show new")) - /* i18n-hint: Radio button to show just the newly discovered effects */ - .AddRadioButtonToGroup(XXO("Ne&w")); -#if wxUSE_ACCESSIBILITY - // so that name can be set on a standard control - rb->SetAccessible(safenew WindowAccessible(rb)); -#endif - } - S.EndHorizontalLay(); - } - S.EndHorizontalLay(); - - mEffects = S.Id(ID_List) - .Style(wxSUNKEN_BORDER | wxLC_REPORT | wxLC_HRULES | wxLC_VRULES ) - .ConnectRoot(wxEVT_KEY_DOWN, - &PluginRegistrationDialog::OnListChar) - .AddListControlReportMode({ XO("Name"), XO("State"), XO("Path") }); -#if wxUSE_ACCESSIBILITY - mEffects->SetAccessible(mAx = safenew CheckListAx(mEffects)); -#endif - - S.StartHorizontalLay(wxALIGN_LEFT | wxEXPAND, 0); - { - S.Id(ID_SelectAll).AddButton(XXO("&Select All")); - S.Id(ID_ClearAll).AddButton(XXO("C&lear All")); - - S.StartHorizontalLay(wxALIGN_CENTER); - { - S.AddSpace(1); - } - S.EndHorizontalLay(); - - S.Id(ID_Enable).AddButton(XXO("&Enable")); - S.Id(ID_Disable).AddButton(XXO("&Disable")); - } - S.EndHorizontalLay(); - } -// S.EndStatic(); - S.EndVerticalLay(); - - S.AddStandardButtons(eOkButton | eCancelButton); - } - S.EndVerticalLay(); - - std::vector colWidths; - for (int i = 0, cnt = mEffects->GetColumnCount(); i < cnt; i++) - { - colWidths.push_back(0); - } - - for (int i = 0, cnt = mStates.size(); i < cnt; i++) - { - int x; - mEffects->GetTextExtent(mStates[i], &x, NULL); - colWidths[COL_State] = wxMax(colWidths[COL_State], x + 4); // 2 pixel margin on each side - } - - PluginManager & pm = PluginManager::Get(); - for (auto &plug : pm.AllPlugins()) { - PluginType plugType = plug.GetPluginType(); - if (plugType != PluginTypeEffect && plugType != PluginTypeStub) - continue; - - const auto &path = plug.GetPath(); - ItemData & item = mItems[path]; // will create NEW entry - item.plugs.push_back(&plug); - item.path = path; - item.state = plug.IsEnabled() ? STATE_Enabled : STATE_Disabled; - item.valid = plug.IsValid(); - - if (plugType == PluginTypeEffect) - { - item.name = plug.GetSymbol().Translation(); - } - // This is not right and will not work when other plugin types are added. - // But it's presumed that the plugin manager dialog will be fully developed - // by then. - else if (plugType == PluginTypeStub) - { - wxFileName fname { path }; - item.name = fname.GetName().Trim(false).Trim(true); - if (!item.valid) - { - item.state = STATE_New; - } - } - - int x; - mEffects->GetTextExtent(item.name, &x, NULL); - colWidths[COL_Name] = wxMax(colWidths[COL_Name], x); - - mEffects->GetTextExtent(item.path, &x, NULL); - if (x > colWidths[COL_Path]) - { - mLongestPath = item.path; - } - colWidths[COL_Path] = wxMax(colWidths[COL_Path], x); - } - - wxRect r = wxGetClientDisplayRect(); - - int maxW = 0; - for (int i = 0, cnt = mEffects->GetColumnCount(); i < cnt; i++) - { - int w = colWidths[i] + /* fudge */ 10; - mEffects->SetColumnWidth(i, w); - maxW += w; - } - - // Keep dialog from getting too wide - int w = r.GetWidth() - (GetClientSize().GetWidth() - mEffects->GetSize().GetWidth()); - mEffects->SetMinSize({ std::min(maxW, w), 200 }); - mEffects->SetMaxSize({ w, -1 }); - - RegenerateEffectsList(ID_ShowAll); - - Layout(); - Fit(); - - wxSize sz = GetSize(); - sz.SetWidth(wxMin(sz.GetWidth(), r.GetWidth())); - sz.SetHeight(wxMin(sz.GetHeight(), r.GetHeight())); - SetMinSize(sz); - - // Parent window is usually not there yet, so centre on screen rather than on parent. - CenterOnScreen(); - - if (mEffects->GetItemCount() > 0) - { - // Make sure first item is selected/focused. - mEffects->SetFocus(); - mEffects->SetItemState(0, wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED, wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED); -#if wxUSE_ACCESSIBILITY - mAx->SetSelected(0); -#endif - } - -} - -void PluginRegistrationDialog::RegenerateEffectsList(int filter) -{ - mFilter = filter; - - mEffects->DeleteAllItems(); - - int i = 0; - for (ItemDataMap::iterator iter = mItems.begin(); iter != mItems.end(); ++iter) - { - ItemData & item = iter->second; - bool add = false; - - switch (mFilter) - { - case ID_ShowAll: - add = true; - break; - case ID_ShowNew: - if (item.state == STATE_New) - { - add = true; - } - break; - case ID_ShowEnabled: - if (item.state == STATE_Enabled) - { - add = true; - } - break; - case ID_ShowDisabled: - if (item.state == STATE_Disabled) - { - add = true; - } - break; - } - - if (add) - { - mEffects->InsertItem(i, item.name); - mEffects->SetItem(i, COL_State, mStates[item.state]); - mEffects->SetItem(i, COL_Path, item.path); - mEffects->SetItemPtrData(i, (wxUIntPtr) &item); - - ++i; - } - } - - mEffects->SortItems(SortCompare, (wxUIntPtr) this); - - if (mEffects->GetItemCount() > 0) - { - // Make sure first item is selected/focused. -// mEffects->SetFocus(); - mEffects->SetItemState(0, wxLIST_STATE_FOCUSED|wxLIST_STATE_SELECTED, wxLIST_STATE_FOCUSED|wxLIST_STATE_SELECTED); -#if wxUSE_ACCESSIBILITY - mAx->SetSelected(0, false); -#endif - } -} - -void PluginRegistrationDialog::SetState(int i, bool toggle, bool state) -{ - wxListItem li; - - li.m_mask = wxLIST_MASK_DATA; - li.m_itemId = i; - - mEffects->GetItem(li); - - ItemData *item = (ItemData *) li.m_data; - - // If changing the state of a "New" (stub) entry, then we mark it as valid - // since it will either be registered if "Enabled" or ignored if "Disabled". - if (item->state == STATE_New) - { - item->valid = true; - } - - if (toggle) - { - item->state = item->state == STATE_Enabled ? STATE_Disabled : STATE_Enabled; - } - else - { - item->state = state; - } - - if (mFilter == ID_ShowNew && item->state != STATE_New) - { - mEffects->DeleteItem(i); - } - else if (mFilter == ID_ShowDisabled && item->state != STATE_Disabled) - { - mEffects->DeleteItem(i); - } - else if (mFilter == ID_ShowEnabled && item->state != STATE_Enabled) - { - mEffects->DeleteItem(i); - } - else - { - mEffects->SetItem(i, COL_State, mStates[item->state]); -#if wxUSE_ACCESSIBILITY - mAx->SetSelected(i); -#endif - } -} - -int wxCALLBACK PluginRegistrationDialog::SortCompare(wxIntPtr item1, wxIntPtr item2, wxIntPtr sortData) -{ - PluginRegistrationDialog *dlg = (PluginRegistrationDialog *) sortData; - ItemData *i1 = (ItemData *) item1; - ItemData *i2 = (ItemData *) item2; - - return dlg->SortCompare(i1, i2); -} - -int PluginRegistrationDialog::SortCompare(ItemData *item1, ItemData *item2) -{ - // This function is a three-valued comparator - - wxString *str1; - wxString *str2; - - switch (mSortColumn) - { - case COL_Name: - str1 = &item1->name; - str2 = &item2->name; - break; - case COL_State: - str1 = &mStates[item1->state]; - str2 = &mStates[item2->state]; - break; - case COL_Path: - str1 = &item1->path; - str2 = &item2->path; - break; - default: - return 0; - } - - return str2->CmpNoCase(*str1) * mSortDirection; -} - -void PluginRegistrationDialog::OnChangedVisibility(wxCommandEvent & evt) -{ - // Go and show the relevant items. - RegenerateEffectsList(evt.GetId()); -} - -void PluginRegistrationDialog::OnSort(wxListEvent & evt) -{ - int col = evt.GetColumn(); - DoSort( col ); -} - -void PluginRegistrationDialog::DoSort( int col ) -{ - if (col != mSortColumn) - { - mSortDirection = 1; - } - else - { - mSortDirection *= -1; - } - - mSortColumn = col; - mEffects->SortItems(SortCompare, (wxUIntPtr) this); - - // Without a refresh, wxMac doesn't redisplay the list properly after a sort - mEffects->Refresh(); -} - -void PluginRegistrationDialog::OnListChar(wxKeyEvent & evt) -{ - switch (evt.GetKeyCode()) - { - case WXK_SPACE: - { - int item = mEffects->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_FOCUSED); - if (item != wxNOT_FOUND) - { - SetState(item, true); - } - } - break; - - case WXK_RETURN: - // Don't know why wxListCtrls prevent default dialog action, - // but they do, so handle it. - EmulateButtonClickIfPresent(GetAffirmativeId()); - break; - - default: - evt.Skip(); - break; - } -} - -void PluginRegistrationDialog::OnSelectAll(wxCommandEvent & WXUNUSED(evt)) -{ - for (int i = 0, cnt = mEffects->GetItemCount(); i < cnt; i++) - { - mEffects->SetItemState(i, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); - } -} - -void PluginRegistrationDialog::OnClearAll(wxCommandEvent & WXUNUSED(evt)) -{ - for (int i = 0, cnt = mEffects->GetItemCount(); i < cnt; i++) - { - mEffects->SetItemState(i, 0, wxLIST_STATE_SELECTED); - } -} - -void PluginRegistrationDialog::OnEnable(wxCommandEvent & WXUNUSED(evt)) -{ - std::vector items; - - { - long i = mEffects->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); - while (i != wxNOT_FOUND) - { - items.insert(items.begin(), i); - i = mEffects->GetNextItem(i, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); - } - } - - for (size_t i = 0, cnt = items.size(); i < cnt; i++) - { - SetState(items[i], false, STATE_Enabled); - } -} - -void PluginRegistrationDialog::OnDisable(wxCommandEvent & WXUNUSED(evt)) -{ - std::vector items; - - { - long i = mEffects->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); - while (i != wxNOT_FOUND) - { - items.insert(items.begin(), i); - i = mEffects->GetNextItem(i, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); - } - } - - for (size_t i = 0, cnt = items.size(); i < cnt; i++) - { - SetState(items[i], false, STATE_Disabled); - } -} - -void PluginRegistrationDialog::OnOK(wxCommandEvent & WXUNUSED(evt)) -{ - PluginManager & pm = PluginManager::Get(); - ModuleManager & mm = ModuleManager::Get(); - - int enableCount = 0; - for (ItemDataMap::iterator iter = mItems.begin(); iter != mItems.end(); ++iter) - { - ItemData & item = iter->second; - wxString path = item.path; - - if (item.state == STATE_Enabled && item.plugs[0]->GetPluginType() == PluginTypeStub) - { - enableCount++; - } - } - - wxString last3 = mLongestPath + wxT("\n") + - mLongestPath + wxT("\n") + - mLongestPath + wxT("\n"); - - auto msg = XO("Enabling effects or commands:\n\n%s").Format( last3 ); - - // Make sure the progress dialog is deleted before we call EndModal() or - // we will leave the project window in an unusable state on OSX. - // See bug #1192. - { - ProgressDialog progress{ - Verbatim( GetTitle() ), msg, pdlgHideStopButton }; - progress.CenterOnParent(); - - int i = 0; - for (ItemDataMap::iterator iter = mItems.begin(); iter != mItems.end(); ++iter) - { - ItemData & item = iter->second; - wxString path = item.path; - - if (item.state == STATE_Enabled && item.plugs[0]->GetPluginType() == PluginTypeStub) - { - last3 = last3.AfterFirst(wxT('\n')) + item.path + wxT("\n"); - auto status = progress.Update(++i, enableCount, - XO("Enabling effect or command:\n\n%s").Format( last3 )); - if (status == ProgressResult::Cancelled) - { - break; - } - - TranslatableString errMsgs; - - // Try to register the plugin via each provider until one succeeds - for (size_t j = 0, cntj = item.plugs.size(); j < cntj; j++) - { - TranslatableString errMsg; - if (mm.RegisterEffectPlugin(item.plugs[j]->GetProviderID(), path, - errMsg)) - { - for (auto plug : item.plugs) - pm.UnregisterPlugin( - plug->GetProviderID() + wxT("_") + path); - // Bug 1893. We've found a provider that works. - // Error messages from any that failed are no longer useful. - errMsgs = {}; - break; - } - else - { - if (!errMsgs.empty()) - errMsgs.Join( errMsg, '\n' ); - else - errMsgs = errMsg; - } - } - if (!errMsgs.empty()) - AudacityMessageBox( - XO("Effect or Command at %s failed to register:\n%s") - .Format( path, errMsgs ) ); - } - else if (item.state == STATE_New) { - for (auto plug : item.plugs) - plug->SetValid(false); - } - else if (item.state != STATE_New) { - for (auto plug : item.plugs) { - plug->SetEnabled(item.state == STATE_Enabled); - plug->SetValid(item.valid); - } - } - } - - pm.Save(); - } - - EndModal(wxID_OK); -} - -void PluginRegistrationDialog::OnCancel(wxCommandEvent & WXUNUSED(evt)) -{ - EndModal(wxID_CANCEL); -} - - /////////////////////////////////////////////////////////////////////////////// // @@ -2379,14 +1410,6 @@ void PluginManager::CheckForUpdates(bool bFast) return; } -bool PluginManager::ShowManager(wxWindow *parent, EffectType type) -{ - CheckForUpdates(); - - PluginRegistrationDialog dlg(parent, type); - return dlg.ShowModal() == wxID_OK; -} - // Here solely for the purpose of Nyquist Workbench until // a better solution is devised. const PluginID & PluginManager::RegisterPlugin( diff --git a/src/PluginManager.h b/src/PluginManager.h index 9317253e0..9013cc678 100644 --- a/src/PluginManager.h +++ b/src/PluginManager.h @@ -293,8 +293,6 @@ public: void CheckForUpdates(bool bFast = false); - bool ShowManager(wxWindow *parent, EffectType type = EffectTypeNone); - //! Used only by Nyquist Workbench module const PluginID & RegisterPlugin( std::unique_ptr effect, PluginType type ); diff --git a/src/PluginRegistrationDialog.cpp b/src/PluginRegistrationDialog.cpp index d4600e322..1d2d4b21e 100644 --- a/src/PluginRegistrationDialog.cpp +++ b/src/PluginRegistrationDialog.cpp @@ -1,59 +1,31 @@ -/********************************************************************** +/*!********************************************************************* Audacity: A Digital Audio Editor - PluginManager.cpp + @file PluginRegistrationDialog.cpp - Leland Lucius - -*******************************************************************//*! - -\file PluginManager.cpp -\brief - -************************************************************************//** -\class PluginManager -\brief PluginManager maintains a list of all plug ins. That covers modules, -effects, generators, analysis-effects, commands. It also has functions -for shared and private configs - which need to move out. -*****************************************************************************/ + Paul Licameli split from PluginManager.cpp +**********************************************************************/ +#include "PluginRegistrationDialog.h" +#include "audacity/EffectInterface.h" +#include "ModuleManager.h" #include "PluginManager.h" - - - -#include +#include "ShuttleGui.h" +#include "widgets/AudacityMessageBox.h" +#include "widgets/ProgressDialog.h" #include // for wxUSE_* macros #include -#include #include #include #include #include -#include #include -#include -#include #include #include -#include "audacity/EffectInterface.h" -#include "audacity/ModuleInterface.h" - -#include "AudacityFileConfig.h" -#include "FileNames.h" -#include "ModuleManager.h" -#include "PlatformCompatibility.h" -#include "Prefs.h" -#include "ShuttleGui.h" -#include "wxFileNameWrapper.h" -#include "widgets/AudacityMessageBox.h" -#include "widgets/ProgressDialog.h" - -#include - // ============================================================================ // // @@ -363,13 +335,6 @@ wxAccStatus CheckListAx::GetValue( int childId, wxString *strValue ) } #endif - -// ============================================================================ -// -// -// -// ============================================================================ - enum { STATE_Enabled, @@ -379,20 +344,6 @@ enum STATE_COUNT }; -struct ItemData -{ - std::vector plugs; - wxString name; - PluginPath path; - int state; - bool valid; - int nameWidth; - int pathWidth; - int stateWidth; -}; - -using ItemDataMap = std::unordered_map; - enum { ID_ShowAll = 10000, @@ -415,52 +366,6 @@ enum COL_COUNT }; -class PluginRegistrationDialog final : public wxDialogWrapper -{ -public: - // constructors and destructors - PluginRegistrationDialog(wxWindow *parent, EffectType type); - -private: - void Populate(); - void PopulateOrExchange(ShuttleGui & S); - void RegenerateEffectsList(int iShowWhat); - void SetState(int i, bool toggle, bool state = true); - - static int wxCALLBACK SortCompare(wxIntPtr item1, wxIntPtr item2, wxIntPtr sortData); - int SortCompare(ItemData *item1, ItemData *item2); - - void OnChangedVisibility(wxCommandEvent & evt); - void OnSort(wxListEvent & evt); - void DoSort( int col ); - void OnListChar(wxKeyEvent & evt); - void OnOK(wxCommandEvent & evt); - void OnCancel(wxCommandEvent & evt); - void OnSelectAll(wxCommandEvent & evt); - void OnClearAll(wxCommandEvent & evt); - void OnEnable(wxCommandEvent & evt); - void OnDisable(wxCommandEvent & evt); - -private: - EffectType mType; - int mFilter; - - wxArrayString mStates; - ItemDataMap mItems; - - int mSortColumn; - int mSortDirection; - - PluginPath mLongestPath; - - wxListCtrl *mEffects; -#if wxUSE_ACCESSIBILITY - CheckListAx *mAx; -#endif - - DECLARE_EVENT_TABLE() -}; - BEGIN_EVENT_TABLE(PluginRegistrationDialog, wxDialogWrapper) EVT_LIST_COL_CLICK(ID_List, PluginRegistrationDialog::OnSort) EVT_BUTTON(wxID_OK, PluginRegistrationDialog::OnOK) @@ -512,7 +417,7 @@ void PluginRegistrationDialog::PopulateOrExchange(ShuttleGui &S) { S.StartVerticalLay(true); { - /*i18n-hint: The dialog shows a list of plugins with check-boxes + /*i18n-hint: The dialog shows a list of plugins with check-boxes beside each one.*/ // S.StartStatic(XO("Effects"), true); S.StartVerticalLay(); @@ -1063,2077 +968,3 @@ void PluginRegistrationDialog::OnCancel(wxCommandEvent & WXUNUSED(evt)) { EndModal(wxID_CANCEL); } - - - -/////////////////////////////////////////////////////////////////////////////// -// -// Plugindescriptor -// -/////////////////////////////////////////////////////////////////////////////// - -PluginDescriptor::PluginDescriptor() -{ - mPluginType = PluginTypeNone; - mEnabled = false; - mValid = false; - mInstance = nullptr; - - mEffectType = EffectTypeNone; - mEffectInteractive = false; - mEffectDefault = false; - mEffectLegacy = false; - mEffectRealtime = false; - mEffectAutomatable = false; -} - -PluginDescriptor::~PluginDescriptor() -{ -} - -PluginDescriptor &PluginDescriptor::operator =(PluginDescriptor &&) = default; - -bool PluginDescriptor::IsInstantiated() const -{ - return mInstance != nullptr; -} - -ComponentInterface *PluginDescriptor::GetInstance() -{ - if (!mInstance) - { - if (GetPluginType() == PluginTypeModule) - mInstance = ModuleManager::Get().CreateProviderInstance(GetID(), GetPath()); - else - { - muInstance = ModuleManager::Get().CreateInstance(GetProviderID(), GetPath()); - mInstance = muInstance.get(); - } - } - - return mInstance; -} - -void PluginDescriptor::SetInstance(std::unique_ptr instance) -{ - muInstance = std::move(instance); - mInstance = muInstance.get(); -} - -PluginType PluginDescriptor::GetPluginType() const -{ - return mPluginType; -} - -const PluginID & PluginDescriptor::GetID() const -{ - return mID; -} - -const PluginID & PluginDescriptor::GetProviderID() const -{ - return mProviderID; -} - -const PluginPath & PluginDescriptor::GetPath() const -{ - return mPath; -} - -const ComponentInterfaceSymbol & PluginDescriptor::GetSymbol() const -{ - return mSymbol; -} - -wxString PluginDescriptor::GetUntranslatedVersion() const -{ - return mVersion; -} - -wxString PluginDescriptor::GetVendor() const -{ - return mVendor; -} - -bool PluginDescriptor::IsEnabled() const -{ - return mEnabled; -} - -bool PluginDescriptor::IsValid() const -{ - return mValid; -} - -void PluginDescriptor::SetPluginType(PluginType type) -{ - mPluginType = type; -} - -void PluginDescriptor::SetID(const PluginID & ID) -{ - mID = ID; -} - -void PluginDescriptor::SetProviderID(const PluginID & providerID) -{ - mProviderID = providerID; -} - -void PluginDescriptor::SetPath(const PluginPath & path) -{ - mPath = path; -} - -void PluginDescriptor::SetSymbol(const ComponentInterfaceSymbol & symbol) -{ - mSymbol = symbol; -} - -void PluginDescriptor::SetVersion(const wxString & version) -{ - mVersion = version; -} - -void PluginDescriptor::SetVendor(const wxString & vendor) -{ - mVendor = vendor; -} - -void PluginDescriptor::SetEnabled(bool enable) -{ - mEnabled = enable; -} - -void PluginDescriptor::SetValid(bool valid) -{ - mValid = valid; -} - -// Effects - -wxString PluginDescriptor::GetEffectFamily() const -{ - return mEffectFamily; -} - -EffectType PluginDescriptor::GetEffectType() const -{ - return mEffectType; -} - -bool PluginDescriptor::IsEffectInteractive() const -{ - return mEffectInteractive; -} - -bool PluginDescriptor::IsEffectDefault() const -{ - return mEffectDefault; -} - -bool PluginDescriptor::IsEffectLegacy() const -{ - return mEffectLegacy; -} - -bool PluginDescriptor::IsEffectRealtime() const -{ - return mEffectRealtime; -} - -bool PluginDescriptor::IsEffectAutomatable() const -{ - return mEffectAutomatable; -} - -void PluginDescriptor::SetEffectFamily(const wxString & family) -{ - mEffectFamily = family; -} - -void PluginDescriptor::SetEffectType(EffectType type) -{ - mEffectType = type; -} - -void PluginDescriptor::SetEffectInteractive(bool interactive) -{ - mEffectInteractive = interactive; -} - -void PluginDescriptor::SetEffectDefault(bool dflt) -{ - mEffectDefault = dflt; -} - -void PluginDescriptor::SetEffectLegacy(bool legacy) -{ - mEffectLegacy = legacy; -} - -void PluginDescriptor::SetEffectRealtime(bool realtime) -{ - mEffectRealtime = realtime; -} - -void PluginDescriptor::SetEffectAutomatable(bool automatable) -{ - mEffectAutomatable = automatable; -} - -// Importer - -const wxString & PluginDescriptor::GetImporterIdentifier() const -{ - return mImporterIdentifier; -} - -void PluginDescriptor::SetImporterIdentifier(const wxString & identifier) -{ - mImporterIdentifier = identifier; -} - -const FileExtensions & PluginDescriptor::GetImporterExtensions() - const -{ - return mImporterExtensions; -} - -void PluginDescriptor::SetImporterExtensions( FileExtensions extensions ) -{ - mImporterExtensions = std::move( extensions ); -} - -/////////////////////////////////////////////////////////////////////////////// -// -// PluginManager -// -/////////////////////////////////////////////////////////////////////////////// - -// Registry has the list of plug ins -#define REGVERKEY wxString(wxT("/pluginregistryversion")) -#define REGVERCUR wxString(wxT("1.1")) -#define REGROOT wxString(wxT("/pluginregistry/")) - -// Settings has the values of the plug in settings. -#define SETVERKEY wxString(wxT("/pluginsettingsversion")) -#define SETVERCUR wxString(wxT("1.0")) -#define SETROOT wxString(wxT("/pluginsettings/")) - -#define KEY_ID wxT("ID") -#define KEY_PATH wxT("Path") -#define KEY_SYMBOL wxT("Symbol") -#define KEY_NAME wxT("Name") -#define KEY_VENDOR wxT("Vendor") -#define KEY_VERSION wxT("Version") -#define KEY_DESCRIPTION wxT("Description") -#define KEY_LASTUPDATED wxT("LastUpdated") -#define KEY_ENABLED wxT("Enabled") -#define KEY_VALID wxT("Valid") -#define KEY_PROVIDERID wxT("ProviderID") -#define KEY_EFFECTTYPE wxT("EffectType") -#define KEY_EFFECTFAMILY wxT("EffectFamily") -#define KEY_EFFECTDEFAULT wxT("EffectDefault") -#define KEY_EFFECTINTERACTIVE wxT("EffectInteractive") -#define KEY_EFFECTREALTIME wxT("EffectRealtime") -#define KEY_EFFECTAUTOMATABLE wxT("EffectAutomatable") -#define KEY_EFFECTTYPE_NONE wxT("None") -#define KEY_EFFECTTYPE_ANALYZE wxT("Analyze") -#define KEY_EFFECTTYPE_GENERATE wxT("Generate") -#define KEY_EFFECTTYPE_PROCESS wxT("Process") -#define KEY_EFFECTTYPE_TOOL wxT("Tool") -#define KEY_EFFECTTYPE_HIDDEN wxT("Hidden") -#define KEY_IMPORTERIDENT wxT("ImporterIdent") -//#define KEY_IMPORTERFILTER wxT("ImporterFilter") -#define KEY_IMPORTEREXTENSIONS wxT("ImporterExtensions") - -// ============================================================================ -// -// PluginManagerInterface implementation -// -// ============================================================================ - -const PluginID &PluginManagerInterface::DefaultRegistrationCallback( - ModuleInterface *provider, ComponentInterface *pInterface ) -{ - EffectDefinitionInterface * pEInterface = dynamic_cast(pInterface); - if( pEInterface ) - return PluginManager::Get().RegisterPlugin(provider, pEInterface, PluginTypeEffect); - ComponentInterface * pCInterface = dynamic_cast(pInterface); - if( pCInterface ) - return PluginManager::Get().RegisterPlugin(provider, pCInterface); - static wxString empty; - return empty; -} - -const PluginID &PluginManagerInterface::AudacityCommandRegistrationCallback( - ModuleInterface *provider, ComponentInterface *pInterface ) -{ - ComponentInterface * pCInterface = dynamic_cast(pInterface); - if( pCInterface ) - return PluginManager::Get().RegisterPlugin(provider, pCInterface); - static wxString empty; - return empty; -} - -RegistryPath PluginManager::GetPluginEnabledSetting( const PluginID &ID ) const -{ - auto pPlugin = GetPlugin( ID ); - if ( pPlugin ) - return GetPluginEnabledSetting( *pPlugin ); - return {}; -} - -RegistryPath PluginManager::GetPluginEnabledSetting( - const PluginDescriptor &desc ) const -{ - switch ( desc.GetPluginType() ) { - case PluginTypeModule: { - // Retrieve optional family symbol that was recorded in - // RegisterPlugin() for the module - auto family = desc.GetEffectFamily(); - if ( family.empty() ) // as for built-in effect and command modules - return {}; - else - return wxT('/') + family + wxT("/Enable"); - } - case PluginTypeEffect: - // do NOT use GetEffectFamily() for this descriptor, but instead, - // delegate to the plugin descriptor of the provider, which may - // be different (may be empty) - return GetPluginEnabledSetting( desc.GetProviderID() ); - default: - return {}; - } -} - -bool PluginManager::IsPluginRegistered( - const PluginPath &path, const TranslatableString *pName) -{ - for (auto &pair : mPlugins) { - if (auto &descriptor = pair.second; descriptor.GetPath() == path) { - if (pName) - descriptor.SetSymbol( - { descriptor.GetSymbol().Internal(), *pName }); - return true; - } - } - return false; -} - -const PluginID & PluginManager::RegisterPlugin(ModuleInterface *module) -{ - PluginDescriptor & plug = CreatePlugin(GetID(module), module, PluginTypeModule); - plug.SetEffectFamily(module->GetOptionalFamilySymbol().Internal()); - - plug.SetEnabled(true); - plug.SetValid(true); - - return plug.GetID(); -} - -const PluginID & PluginManager::RegisterPlugin(ModuleInterface *provider, ComponentInterface *command) -{ - PluginDescriptor & plug = CreatePlugin(GetID(command), command, (PluginType)PluginTypeAudacityCommand); - - plug.SetProviderID(PluginManager::GetID(provider)); - - plug.SetEnabled(true); - plug.SetValid(true); - - return plug.GetID(); -} - -const PluginID & PluginManager::RegisterPlugin(ModuleInterface *provider, EffectDefinitionInterface *effect, int type) -{ - PluginDescriptor & plug = CreatePlugin(GetID(effect), effect, (PluginType)type); - - plug.SetProviderID(PluginManager::GetID(provider)); - - plug.SetEffectType(effect->GetClassification()); - plug.SetEffectFamily(effect->GetFamily().Internal()); - plug.SetEffectInteractive(effect->IsInteractive()); - plug.SetEffectDefault(effect->IsDefault()); - plug.SetEffectRealtime(effect->SupportsRealtime()); - plug.SetEffectAutomatable(effect->SupportsAutomation()); - - plug.SetEnabled(true); - plug.SetValid(true); - - return plug.GetID(); -} - -const PluginID & PluginManager::RegisterPlugin(ModuleInterface *provider, ImporterInterface *importer) -{ - PluginDescriptor & plug = CreatePlugin(GetID(importer), importer, PluginTypeImporter); - - plug.SetProviderID(PluginManager::GetID(provider)); - - plug.SetImporterIdentifier(importer->GetPluginStringID()); - plug.SetImporterExtensions(importer->GetSupportedExtensions()); - - return plug.GetID(); -} - -void PluginManager::FindFilesInPathList(const wxString & pattern, - const FilePaths & pathList, - FilePaths & files, - bool directories) -{ - - wxLogNull nolog; - - // Why bother... - if (pattern.empty()) - { - return; - } - - // TODO: We REALLY need to figure out the "Audacity" plug-in path(s) - - FilePaths paths; - - // Add the "per-user" plug-ins directory - { - const wxFileName &ff = FileNames::PlugInDir(); - paths.push_back(ff.GetFullPath()); - } - - // Add the "Audacity" plug-ins directory - wxFileName ff = PlatformCompatibility::GetExecutablePath(); -#if defined(__WXMAC__) - // Path ends for example in "Audacity.app/Contents/MacOSX" - //ff.RemoveLastDir(); - //ff.RemoveLastDir(); - // just remove the MacOSX part. - ff.RemoveLastDir(); -#endif - ff.AppendDir(wxT("plug-ins")); - paths.push_back(ff.GetPath()); - - // Weed out duplicates - for (const auto &filePath : pathList) - { - ff = filePath; - const wxString path{ ff.GetFullPath() }; - if (paths.Index(path, wxFileName::IsCaseSensitive()) == wxNOT_FOUND) - { - paths.push_back(path); - } - } - - // Find all matching files in each path - for (size_t i = 0, cnt = paths.size(); i < cnt; i++) - { - ff = paths[i] + wxFILE_SEP_PATH + pattern; - wxDir::GetAllFiles(ff.GetPath(), &files, ff.GetFullName(), directories ? wxDIR_DEFAULT : wxDIR_FILES); - } - - return; -} - -bool PluginManager::HasSharedConfigGroup(const PluginID & ID, const RegistryPath & group) -{ - return HasGroup(SharedGroup(ID, group)); -} - -bool PluginManager::GetSharedConfigSubgroups(const PluginID & ID, const RegistryPath & group, RegistryPaths & subgroups) -{ - return GetSubgroups(SharedGroup(ID, group), subgroups); -} - -bool PluginManager::GetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, wxString & value, const wxString & defval) -{ - return GetConfig(SharedKey(ID, group, key), value, defval); -} - -bool PluginManager::GetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, int & value, int defval) -{ - return GetConfig(SharedKey(ID, group, key), value, defval); -} - -bool PluginManager::GetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, bool & value, bool defval) -{ - return GetConfig(SharedKey(ID, group, key), value, defval); -} - -bool PluginManager::GetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, float & value, float defval) -{ - return GetConfig(SharedKey(ID, group, key), value, defval); -} - -bool PluginManager::GetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, double & value, double defval) -{ - return GetConfig(SharedKey(ID, group, key), value, defval); -} - -bool PluginManager::SetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const wxString & value) -{ - return SetConfig(SharedKey(ID, group, key), value); -} - -bool PluginManager::SetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const int & value) -{ - return SetConfig(SharedKey(ID, group, key), value); -} - -bool PluginManager::SetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const bool & value) -{ - return SetConfig(SharedKey(ID, group, key), value); -} - -bool PluginManager::SetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const float & value) -{ - return SetConfig(SharedKey(ID, group, key), value); -} - -bool PluginManager::SetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const double & value) -{ - return SetConfig(SharedKey(ID, group, key), value); -} - -bool PluginManager::RemoveSharedConfigSubgroup(const PluginID & ID, const RegistryPath & group) -{ - bool result = GetSettings()->DeleteGroup(SharedGroup(ID, group)); - if (result) - { - GetSettings()->Flush(); - } - - return result; -} - -bool PluginManager::RemoveSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key) -{ - bool result = GetSettings()->DeleteEntry(SharedKey(ID, group, key)); - if (result) - { - GetSettings()->Flush(); - } - - return result; -} - -bool PluginManager::HasPrivateConfigGroup(const PluginID & ID, const RegistryPath & group) -{ - return HasGroup(PrivateGroup(ID, group)); -} - -bool PluginManager::GetPrivateConfigSubgroups(const PluginID & ID, const RegistryPath & group, RegistryPaths & subgroups) -{ - return GetSubgroups(PrivateGroup(ID, group), subgroups); -} - -bool PluginManager::GetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, wxString & value, const wxString & defval) -{ - return GetConfig(PrivateKey(ID, group, key), value, defval); -} - -bool PluginManager::GetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, int & value, int defval) -{ - return GetConfig(PrivateKey(ID, group, key), value, defval); -} - -bool PluginManager::GetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, bool & value, bool defval) -{ - return GetConfig(PrivateKey(ID, group, key), value, defval); -} - -bool PluginManager::GetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, float & value, float defval) -{ - return GetConfig(PrivateKey(ID, group, key), value, defval); -} - -bool PluginManager::GetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, double & value, double defval) -{ - return GetConfig(PrivateKey(ID, group, key), value, defval); -} - -bool PluginManager::SetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const wxString & value) -{ - return SetConfig(PrivateKey(ID, group, key), value); -} - -bool PluginManager::SetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const int & value) -{ - return SetConfig(PrivateKey(ID, group, key), value); -} - -bool PluginManager::SetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const bool & value) -{ - return SetConfig(PrivateKey(ID, group, key), value); -} - -bool PluginManager::SetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const float & value) -{ - return SetConfig(PrivateKey(ID, group, key), value); -} - -bool PluginManager::SetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const double & value) -{ - return SetConfig(PrivateKey(ID, group, key), value); -} - -bool PluginManager::RemovePrivateConfigSubgroup(const PluginID & ID, const RegistryPath & group) -{ - bool result = GetSettings()->DeleteGroup(PrivateGroup(ID, group)); - if (result) - { - GetSettings()->Flush(); - } - - return result; -} - -bool PluginManager::RemovePrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key) -{ - bool result = GetSettings()->DeleteEntry(PrivateKey(ID, group, key)); - if (result) - { - GetSettings()->Flush(); - } - - return result; -} - -// ============================================================================ -// -// PluginManager -// -// ============================================================================ - -// The one and only PluginManager -std::unique_ptr PluginManager::mInstance{}; - -// ---------------------------------------------------------------------------- -// Creation/Destruction -// ---------------------------------------------------------------------------- - -PluginManager::PluginManager() -{ - mSettings = NULL; -} - -PluginManager::~PluginManager() -{ - // Ensure termination (harmless if already done) - Terminate(); -} - -// ---------------------------------------------------------------------------- -// PluginManager implementation -// ---------------------------------------------------------------------------- - -// ============================================================================ -// -// Return reference to singleton -// -// (Thread-safe...no active threading during construction or after destruction) -// ============================================================================ - -PluginManager & PluginManager::Get() -{ - if (!mInstance) - { - mInstance.reset(safenew PluginManager); - } - - return *mInstance; -} - -void PluginManager::Initialize() -{ - // Always load the registry first - Load(); - - // And force load of setting to verify it's accessible - GetSettings(); - - // Then look for providers (they may autoregister plugins) - ModuleManager::Get().DiscoverProviders(); - - // And finally check for updates -#ifndef EXPERIMENTAL_EFFECT_MANAGEMENT - CheckForUpdates(); -#else - const bool kFast = true; - CheckForUpdates( kFast ); -#endif -} - -void PluginManager::Terminate() -{ - // Get rid of all non-module plugins first - PluginMap::iterator iter = mPlugins.begin(); - while (iter != mPlugins.end()) - { - PluginDescriptor & plug = iter->second; - if (plug.GetPluginType() == PluginTypeEffect) - { - mPlugins.erase(iter++); - continue; - } - - ++iter; - } - - // Now get rid of the modules - iter = mPlugins.begin(); - while (iter != mPlugins.end()) - { - mPlugins.erase(iter++); - } -} - -bool PluginManager::DropFile(const wxString &fileName) -{ - auto &mm = ModuleManager::Get(); - const wxFileName src{ fileName }; - - for (auto &plug : PluginsOfType(PluginTypeModule)) { - auto module = static_cast - (mm.CreateProviderInstance(plug.GetID(), plug.GetPath())); - if (! module) - continue; - - const auto &ff = module->InstallPath(); - const auto &extensions = module->GetFileExtensions(); - if ( !ff.empty() && - extensions.Index(src.GetExt(), false) != wxNOT_FOUND ) { - TranslatableString errMsg; - // Do dry-run test of the file format - unsigned nPlugIns = - module->DiscoverPluginsAtPath(fileName, errMsg, {}); - if (nPlugIns) { - // File contents are good for this module, so check no others. - // All branches of this block return true, even in case of - // failure for other reasons, to signal that other drag-and-drop - // actions should not be tried. - - // Find path to copy it - wxFileName dst; - dst.AssignDir( ff ); - dst.SetFullName( src.GetFullName() ); - if ( dst.Exists() ) { - // Query whether to overwrite - bool overwrite = (wxYES == ::AudacityMessageBox( - XO("Overwrite the plug-in file %s?") - .Format( dst.GetFullPath() ), - XO("Plug-in already exists"), - wxYES_NO ) ); - if ( !overwrite ) - return true; - } - - // Move the file or subtree - bool copied = false; - auto dstPath = dst.GetFullPath(); - if ( src.FileExists() ) - // A simple one-file plug-in - copied = FileNames::DoCopyFile( - src.GetFullPath(), dstPath, true ); - else { - // A sub-folder - // such as for some VST packages - // Recursive copy needed -- to do - return true; - } - - if (!copied) { - ::AudacityMessageBox( - XO("Plug-in file is in use. Failed to overwrite") ); - return true; - } - - // Register for real - std::vector ids; - std::vector names; - nPlugIns = module->DiscoverPluginsAtPath(dstPath, errMsg, - [&](ModuleInterface *provider, ComponentInterface *ident) - -> const PluginID& { - // Register as by default, but also collecting the PluginIDs - // and names - auto &id = PluginManagerInterface::DefaultRegistrationCallback( - provider, ident); - ids.push_back(id); - names.push_back( ident->GetSymbol().Translation() ); - return id; - }); - if ( ! nPlugIns ) { - // Unlikely after the dry run succeeded - ::AudacityMessageBox( - XO("Failed to register:\n%s").Format( errMsg ) ); - return true; - } - - // Ask whether to enable the plug-ins - if (auto nIds = ids.size()) { - auto message = XPC( - /* i18n-hint A plug-in is an optional added program for a sound - effect, or generator, or analyzer */ - "Enable this plug-in?\n", - "Enable these plug-ins?\n", - 0, - "plug-ins" - )( nIds ); - for (const auto &name : names) - message.Join( Verbatim( name ), wxT("\n") ); - bool enable = (wxYES == ::AudacityMessageBox( - message, - XO("Enable new plug-ins"), - wxYES_NO ) ); - for (const auto &id : ids) - mPlugins[id].SetEnabled(enable); - // Make changes to enabled status persist: - this->Save(); - } - - return true; - } - } - } - - return false; -} - -void PluginManager::Load() -{ - // Create/Open the registry - auto pRegistry = AudacityFileConfig::Create( - {}, {}, FileNames::PluginRegistry()); - auto ®istry = *pRegistry; - - // If this group doesn't exist then we have something that's not a registry. - // We should probably warn the user, but it's pretty unlikely that this will happen. - if (!registry.HasGroup(REGROOT)) - { - // Must start over - registry.DeleteAll(); - registry.Flush(); - return; - } - - // Check for a registry version that we can understand - // TODO: Should also check for a registry file that is newer than - // what we can understand. - wxString regver = registry.Read(REGVERKEY); - if (regver < REGVERCUR ) - { - // Conversion code here, for when registry version changes. - - // We iterate through the effects, possibly updating their info. - wxString groupName; - long groupIndex; - wxString group = GetPluginTypeString(PluginTypeEffect); - wxString cfgPath = REGROOT + group + wxCONFIG_PATH_SEPARATOR; - wxArrayString groupsToDelete; - - registry.SetPath(cfgPath); - for (bool cont = registry.GetFirstGroup(groupName, groupIndex); - cont; - registry.SetPath(cfgPath), - cont = registry.GetNextGroup(groupName, groupIndex)) - { - registry.SetPath(groupName); - wxString effectSymbol = registry.Read(KEY_SYMBOL, ""); - wxString effectVersion = registry.Read(KEY_VERSION, ""); - - - // For 2.3.0 the plugins we distribute have moved around. - // So we upped the registry version number to 1.1. - // These particular config edits were originally written to fix Bug 1914. - if (regver <= "1.0") { - // Nyquist prompt is a built-in that has moved to the tools menu. - if (effectSymbol == NYQUIST_PROMPT_ID) { - registry.Write(KEY_EFFECTTYPE, "Tool"); - // Old version of SDE was in Analyze menu. Now it is in Tools. - // We don't want both the old and the new. - } else if ((effectSymbol == "Sample Data Export") && (effectVersion == "n/a")) { - groupsToDelete.push_back(cfgPath + groupName); - // Old version of SDI was in Generate menu. Now it is in Tools. - } else if ((effectSymbol == "Sample Data Import") && (effectVersion == "n/a")) { - groupsToDelete.push_back(cfgPath + groupName); - } - } - - } - // Doing the deletion within the search loop risked skipping some items, - // hence the delayed delete. - for (unsigned int i = 0; i < groupsToDelete.size(); i++) { - registry.DeleteGroup(groupsToDelete[i]); - } - registry.SetPath(""); - registry.Write(REGVERKEY, REGVERCUR); - // Updates done. Make sure we read the updated data later. - registry.Flush(); - } - - // Load all provider plugins first - LoadGroup(®istry, PluginTypeModule); - - // Now the rest - LoadGroup(®istry, PluginTypeEffect); - LoadGroup(®istry, PluginTypeAudacityCommand ); - LoadGroup(®istry, PluginTypeExporter); - LoadGroup(®istry, PluginTypeImporter); - - LoadGroup(®istry, PluginTypeStub); - return; -} - -void PluginManager::LoadGroup(FileConfig *pRegistry, PluginType type) -{ -#ifdef __WXMAC__ - // Bug 1590: On Mac, we should purge the registry of Nyquist plug-ins - // bundled with other versions of Audacity, assuming both versions - // were properly installed in /Applications (or whatever it is called in - // your locale) - - const auto fullExePath = PlatformCompatibility::GetExecutablePath(); - - // Strip rightmost path components up to *.app - wxFileName exeFn{ fullExePath }; - exeFn.SetEmptyExt(); - exeFn.SetName(wxString{}); - while(exeFn.GetDirCount() && !exeFn.GetDirs().back().EndsWith(".app")) - exeFn.RemoveLastDir(); - - const auto goodPath = exeFn.GetPath(); - - if(exeFn.GetDirCount()) - exeFn.RemoveLastDir(); - const auto possiblyBadPath = exeFn.GetPath(); - - auto AcceptPath = [&](const wxString &path) { - if (!path.StartsWith(possiblyBadPath)) - // Assume it's not under /Applications - return true; - if (path.StartsWith(goodPath)) - // It's bundled with this executable - return true; - return false; - }; -#else - auto AcceptPath = [](const wxString&){ return true; }; -#endif - - wxString strVal; - bool boolVal; - wxString groupName; - long groupIndex; - wxString group = GetPluginTypeString(type); - wxString cfgPath = REGROOT + group + wxCONFIG_PATH_SEPARATOR; - - pRegistry->SetPath(cfgPath); - for (bool cont = pRegistry->GetFirstGroup(groupName, groupIndex); - cont; - pRegistry->SetPath(cfgPath), - cont = pRegistry->GetNextGroup(groupName, groupIndex)) - { - PluginDescriptor plug; - - pRegistry->SetPath(groupName); - - groupName = ConvertID(groupName); - - // Bypass group if the ID is already in use - if (mPlugins.count(groupName)) - continue; - - // Set the ID and type - plug.SetID(groupName); - plug.SetPluginType(type); - - // Get the provider ID and bypass group if not found - if (!pRegistry->Read(KEY_PROVIDERID, &strVal, wxEmptyString)) - { - // Bypass group if the provider isn't valid - if (!strVal.empty() && !mPlugins.count(strVal)) - continue; - } - plug.SetProviderID(PluginID(strVal)); - - // Get the path (optional) - pRegistry->Read(KEY_PATH, &strVal, wxEmptyString); - if (!AcceptPath(strVal)) - // Ignore the obsolete path in the config file, during session, - // but don't remove it from the file. Maybe you really want to - // switch back to the other version of Audacity and lose nothing. - continue; - plug.SetPath(strVal); - - /* - // PRL: Ignore names written in configs before 2.3.0! - // use Internal string only! Let the present version of Audacity map - // that to a user-visible string. - // Get the name and bypass group if not found - if (!pRegistry->Read(KEY_NAME, &strVal)) - { - continue; - } - plug.SetName(strVal); - */ - - // Get the symbol...Audacity 2.3.0 or later requires it - // bypass group if not found - // Note, KEY_SYMBOL started getting written to config files in 2.1.0. - // KEY_NAME (now ignored) was written before that, but only for VST - // effects. - if (!pRegistry->Read(KEY_SYMBOL, &strVal)) - continue; - - // Related to Bug2778: config file only remembered an internal name, - // so this symbol may not contain the correct TranslatableString. - // See calls to IsPluginRegistered which can correct that. - plug.SetSymbol(strVal); - - // Get the version and bypass group if not found - if (!pRegistry->Read(KEY_VERSION, &strVal)) - { - continue; - } - plug.SetVersion(strVal); - - // Get the vendor and bypass group if not found - if (!pRegistry->Read(KEY_VENDOR, &strVal)) - { - continue; - } - plug.SetVendor( strVal ); - -#if 0 - // This was done before version 2.2.2, but the value was not really used - // But absence of a value will cause early versions to skip the group - // Therefore we still write a blank to keep pluginregistry.cfg - // backwards-compatible - - // Get the description and bypass group if not found - if (!pRegistry->Read(KEY_DESCRIPTION, &strVal)) - { - continue; - } -#endif - - // Is it enabled...default to no if not found - pRegistry->Read(KEY_ENABLED, &boolVal, false); - plug.SetEnabled(boolVal); - - // Is it valid...default to no if not found - pRegistry->Read(KEY_VALID, &boolVal, false); - plug.SetValid(boolVal); - - switch (type) - { - case PluginTypeModule: - { - // Nothing to do here yet - } - break; - - case PluginTypeEffect: - { - // Get the effect type and bypass group if not found - if (!pRegistry->Read(KEY_EFFECTTYPE, &strVal)) - continue; - - if (strVal == KEY_EFFECTTYPE_NONE) - plug.SetEffectType(EffectTypeNone); - else if (strVal == KEY_EFFECTTYPE_ANALYZE) - plug.SetEffectType(EffectTypeAnalyze); - else if (strVal == KEY_EFFECTTYPE_GENERATE) - plug.SetEffectType(EffectTypeGenerate); - else if (strVal == KEY_EFFECTTYPE_PROCESS) - plug.SetEffectType(EffectTypeProcess); - else if (strVal == KEY_EFFECTTYPE_TOOL) - plug.SetEffectType(EffectTypeTool); - else if (strVal == KEY_EFFECTTYPE_HIDDEN) - plug.SetEffectType(EffectTypeHidden); - else - continue; - - // Get the effect family and bypass group if not found - if (!pRegistry->Read(KEY_EFFECTFAMILY, &strVal)) - { - continue; - } - plug.SetEffectFamily(strVal); - - // Is it a default (above the line) effect and bypass group if not found - if (!pRegistry->Read(KEY_EFFECTDEFAULT, &boolVal)) - { - continue; - } - plug.SetEffectDefault(boolVal); - - // Is it an interactive effect and bypass group if not found - if (!pRegistry->Read(KEY_EFFECTINTERACTIVE, &boolVal)) - { - continue; - } - plug.SetEffectInteractive(boolVal); - - // Is it a realtime capable effect and bypass group if not found - if (!pRegistry->Read(KEY_EFFECTREALTIME, &boolVal)) - { - continue; - } - plug.SetEffectRealtime(boolVal); - - // Does the effect support automation...bypass group if not found - if (!pRegistry->Read(KEY_EFFECTAUTOMATABLE, &boolVal)) - { - continue; - } - plug.SetEffectAutomatable(boolVal); - } - break; - - case PluginTypeImporter: - { - // Get the importer identifier and bypass group if not found - if (!pRegistry->Read(KEY_IMPORTERIDENT, &strVal)) - { - continue; - } - plug.SetImporterIdentifier(strVal); - - // Get the importer extensions and bypass group if not found - if (!pRegistry->Read(KEY_IMPORTEREXTENSIONS, &strVal)) - { - continue; - } - FileExtensions extensions; - wxStringTokenizer tkr(strVal, wxT(":")); - while (tkr.HasMoreTokens()) - { - extensions.push_back(tkr.GetNextToken()); - } - plug.SetImporterExtensions(extensions); - } - break; - - case PluginTypeStub: - { - // Nothing additional for stubs - } - break; - - // Not used by 2.1.1 or greater and should be removed after a few releases past 2.1.0. - case PluginTypeNone: - { - // Used for stub groups - } - break; - - default: - { - continue; - } - } - - // Everything checked out...accept the plugin - mPlugins[groupName] = std::move(plug); - } - - return; -} - -void PluginManager::Save() -{ - // Create/Open the registry - auto pRegistry = AudacityFileConfig::Create( - {}, {}, FileNames::PluginRegistry()); - auto ®istry = *pRegistry; - - // Clear it out - registry.DeleteAll(); - - // Write the version string - registry.Write(REGVERKEY, REGVERCUR); - - // Save the individual groups - SaveGroup(®istry, PluginTypeEffect); - SaveGroup(®istry, PluginTypeExporter); - SaveGroup(®istry, PluginTypeAudacityCommand); - SaveGroup(®istry, PluginTypeImporter); - SaveGroup(®istry, PluginTypeStub); - - // Not used by 2.1.1 or greater, but must save to allow users to switch between 2.1.0 - // and 2.1.1+. This should be removed after a few releases past 2.1.0. - //SaveGroup(®istry, PluginTypeNone); - - // And now the providers - SaveGroup(®istry, PluginTypeModule); - - // Just to be safe - registry.Flush(); -} - -void PluginManager::SaveGroup(FileConfig *pRegistry, PluginType type) -{ - wxString group = GetPluginTypeString(type); - for (auto &pair : mPlugins) { - auto & plug = pair.second; - - if (plug.GetPluginType() != type) - { - continue; - } - - pRegistry->SetPath(REGROOT + group + wxCONFIG_PATH_SEPARATOR + ConvertID(plug.GetID())); - - pRegistry->Write(KEY_PATH, plug.GetPath()); - - // See comments with the corresponding load-time call to SetSymbol(). - pRegistry->Write(KEY_SYMBOL, plug.GetSymbol().Internal()); - - // PRL: Writing KEY_NAME which is no longer read, but older Audacity - // versions expect to find it. - pRegistry->Write(KEY_NAME, plug.GetSymbol().Msgid().MSGID()); - - pRegistry->Write(KEY_VERSION, plug.GetUntranslatedVersion()); - pRegistry->Write(KEY_VENDOR, plug.GetVendor()); - // Write a blank -- see comments in LoadGroup: - pRegistry->Write(KEY_DESCRIPTION, wxString{}); - pRegistry->Write(KEY_PROVIDERID, plug.GetProviderID()); - pRegistry->Write(KEY_ENABLED, plug.IsEnabled()); - pRegistry->Write(KEY_VALID, plug.IsValid()); - - switch (type) - { - case PluginTypeModule: - break; - - case PluginTypeEffect: - { - EffectType etype = plug.GetEffectType(); - wxString stype; - if (etype == EffectTypeNone) - stype = KEY_EFFECTTYPE_NONE; - else if (etype == EffectTypeAnalyze) - stype = KEY_EFFECTTYPE_ANALYZE; - else if (etype == EffectTypeGenerate) - stype = KEY_EFFECTTYPE_GENERATE; - else if (etype == EffectTypeProcess) - stype = KEY_EFFECTTYPE_PROCESS; - else if (etype == EffectTypeTool) - stype = KEY_EFFECTTYPE_TOOL; - else if (etype == EffectTypeHidden) - stype = KEY_EFFECTTYPE_HIDDEN; - - pRegistry->Write(KEY_EFFECTTYPE, stype); - pRegistry->Write(KEY_EFFECTFAMILY, plug.GetEffectFamily()); - pRegistry->Write(KEY_EFFECTDEFAULT, plug.IsEffectDefault()); - pRegistry->Write(KEY_EFFECTINTERACTIVE, plug.IsEffectInteractive()); - pRegistry->Write(KEY_EFFECTREALTIME, plug.IsEffectRealtime()); - pRegistry->Write(KEY_EFFECTAUTOMATABLE, plug.IsEffectAutomatable()); - } - break; - - case PluginTypeImporter: - { - pRegistry->Write(KEY_IMPORTERIDENT, plug.GetImporterIdentifier()); - const auto & extensions = plug.GetImporterExtensions(); - wxString strExt; - for (size_t i = 0, cnt = extensions.size(); i < cnt; i++) - { - strExt += extensions[i] + wxT(":"); - } - strExt.RemoveLast(1); - pRegistry->Write(KEY_IMPORTEREXTENSIONS, strExt); - } - break; - - default: - break; - } - } - - return; -} - -// If bFast is true, do not do a full check. Just check the ones -// that are quick to check. Currently (Feb 2017) just Nyquist -// and built-ins. -void PluginManager::CheckForUpdates(bool bFast) -{ - ModuleManager & mm = ModuleManager::Get(); - wxArrayString pathIndex; - for (auto &pair : mPlugins) { - auto &plug = pair.second; - - // Bypass 2.1.0 placeholders...remove this after a few releases past 2.1.0 - if (plug.GetPluginType() != PluginTypeNone) - pathIndex.push_back(plug.GetPath().BeforeFirst(wxT(';'))); - } - - // Check all known plugins to ensure they are still valid and scan for NEW ones. - // - // All NEW plugins get a stub entry created that will remain in place until the - // user enables or disables the plugin. - // - // Because we use the plugins "path" as returned by the providers, we can actually - // have multiple providers report the same path since, at this point, they only - // know that the path might possibly be one supported by the provider. - // - // When the user enables the plugin, each provider that reported it will be asked - // to register the plugin. - for (auto &pair : mPlugins) { - auto &plug = pair.second; - const PluginID & plugID = plug.GetID(); - const wxString & plugPath = plug.GetPath(); - PluginType plugType = plug.GetPluginType(); - - // Bypass 2.1.0 placeholders...remove this after a few releases past 2.1.0 - if (plugType == PluginTypeNone) - { - continue; - } - - if ( plugType == PluginTypeModule ) - { - if( bFast ) - { - // Skip modules, when doing a fast refresh/check. - } - else if (!mm.IsProviderValid(plugID, plugPath)) - { - plug.SetEnabled(false); - plug.SetValid(false); - } - else - { - // Collect plugin paths - auto paths = mm.FindPluginsForProvider(plugID, plugPath); - for (size_t i = 0, cnt = paths.size(); i < cnt; i++) - { - wxString path = paths[i].BeforeFirst(wxT(';'));; - if ( ! make_iterator_range( pathIndex ).contains( path ) ) - { - PluginID ID = plugID + wxT("_") + path; - PluginDescriptor & plug2 = mPlugins[ID]; // This will create a NEW descriptor - plug2.SetPluginType(PluginTypeStub); - plug2.SetID(ID); - plug2.SetProviderID(plugID); - plug2.SetPath(path); - plug2.SetEnabled(false); - plug2.SetValid(false); - } - } - } - } - else if (plugType != PluginTypeNone && plugType != PluginTypeStub) - { - plug.SetValid(mm.IsPluginValid(plug.GetProviderID(), plugPath, bFast)); - if (!plug.IsValid()) - { - plug.SetEnabled(false); - } - } - } - - Save(); - - return; -} - -bool PluginManager::ShowManager(wxWindow *parent, EffectType type) -{ - CheckForUpdates(); - - PluginRegistrationDialog dlg(parent, type); - return dlg.ShowModal() == wxID_OK; -} - -// Here solely for the purpose of Nyquist Workbench until -// a better solution is devised. -const PluginID & PluginManager::RegisterPlugin( - std::unique_ptr effect, PluginType type) -{ - PluginDescriptor & plug = - CreatePlugin(GetID(effect.get()), effect.get(), type); - - plug.SetEffectType(effect->GetType()); - plug.SetEffectFamily(effect->GetFamily().Internal()); - plug.SetEffectInteractive(effect->IsInteractive()); - plug.SetEffectDefault(effect->IsDefault()); - plug.SetEffectRealtime(effect->SupportsRealtime()); - plug.SetEffectAutomatable(effect->SupportsAutomation()); - - plug.SetInstance(std::move(effect)); - plug.SetEffectLegacy(true); - plug.SetEnabled(true); - plug.SetValid(true); - - return plug.GetID(); -} - -void PluginManager::UnregisterPlugin(const PluginID & ID) -{ - mPlugins.erase(ID); -} - -int PluginManager::GetPluginCount(PluginType type) -{ - return count_if(mPlugins.begin(), mPlugins.end(), [type](auto &pair){ - return pair.second.GetPluginType() == type; }); -} - -const PluginDescriptor *PluginManager::GetPlugin(const PluginID & ID) const -{ - if (auto iter = mPlugins.find(ID); iter == mPlugins.end()) - return nullptr; - else - return &iter->second; -} - -void PluginManager::Iterator::Advance(bool incrementing) -{ - const auto end = mPm.mPlugins.end(); - if (incrementing && mIterator != end) - ++mIterator; - bool all = mPluginType == PluginTypeNone && mEffectType == EffectTypeNone; - for (; mIterator != end; ++mIterator) { - auto &plug = mIterator->second; - if (!all && !(plug.IsValid() && plug.IsEnabled())) - continue; - auto plugType = plug.GetPluginType(); - if ((mPluginType == PluginTypeNone || (plugType & mPluginType)) && - (mEffectType == EffectTypeNone || plug.GetEffectType() == mEffectType)) { - if (!all && (plugType & PluginTypeEffect)) { - // This preference may be written by EffectsPrefs - auto setting = mPm.GetPluginEnabledSetting( plug ); - if (!(setting.empty() || gPrefs->Read( setting, true ))) - continue; - } - // Pause iteration at this match - break; - } - } -} - -PluginManager::Iterator::Iterator(PluginManager &manager) -: mPm{ manager } -, mIterator{ manager.mPlugins.begin() } -{ -} - -PluginManager::Iterator::Iterator(PluginManager &manager, int type) -: mPm{ manager } -, mIterator{ manager.mPlugins.begin() } -, mPluginType{ type } -{ - Advance(false); -} - -PluginManager::Iterator::Iterator(PluginManager &manager, EffectType type) -: mPm{ manager } -, mIterator{ manager.mPlugins.begin() } -, mEffectType{ type } -{ - Advance(false); -} - -auto PluginManager::Iterator::operator ++() -> Iterator & -{ - Advance(true); - return *this; -} - -bool PluginManager::IsPluginEnabled(const PluginID & ID) -{ - if (auto iter = mPlugins.find(ID); iter == mPlugins.end()) - return false; - else - return iter->second.IsEnabled(); -} - -void PluginManager::EnablePlugin(const PluginID & ID, bool enable) -{ - if (auto iter = mPlugins.find(ID); iter == mPlugins.end()) - return; - else - iter->second.SetEnabled(enable); -} - -const ComponentInterfaceSymbol & PluginManager::GetSymbol(const PluginID & ID) -{ - if (auto iter = mPlugins.find(ID); iter == mPlugins.end()) { - static ComponentInterfaceSymbol empty; - return empty; - } - else - return iter->second.GetSymbol(); -} - -ComponentInterface *PluginManager::GetInstance(const PluginID & ID) -{ - if (auto iter = mPlugins.find(ID); iter == mPlugins.end()) - return nullptr; - else { - auto &plug = iter->second; - - // If not dealing with legacy effects, make sure the provider is loaded - if (!plug.IsEffectLegacy()) - { - const PluginID & prov = plug.GetProviderID(); - if (auto iter2 = mPlugins.find(prov); iter2 == mPlugins.end()) - return nullptr; - else - iter2->second.GetInstance(); - } - - return plug.GetInstance(); - } -} - -PluginID PluginManager::GetID(ModuleInterface *module) -{ - return wxString::Format(wxT("%s_%s_%s_%s_%s"), - GetPluginTypeString(PluginTypeModule), - wxEmptyString, - module->GetVendor().Internal(), - module->GetSymbol().Internal(), - module->GetPath()); -} - -PluginID PluginManager::GetID(ComponentInterface *command) -{ - return wxString::Format(wxT("%s_%s_%s_%s_%s"), - GetPluginTypeString(PluginTypeAudacityCommand), - wxEmptyString, - command->GetVendor().Internal(), - command->GetSymbol().Internal(), - command->GetPath()); -} - -PluginID PluginManager::GetID(EffectDefinitionInterface *effect) -{ - return wxString::Format(wxT("%s_%s_%s_%s_%s"), - GetPluginTypeString(PluginTypeEffect), - effect->GetFamily().Internal(), - effect->GetVendor().Internal(), - effect->GetSymbol().Internal(), - effect->GetPath()); -} - -PluginID PluginManager::GetID(ImporterInterface *importer) -{ - return wxString::Format(wxT("%s_%s_%s_%s_%s"), - GetPluginTypeString(PluginTypeImporter), - wxEmptyString, - importer->GetVendor().Internal(), - importer->GetSymbol().Internal(), - importer->GetPath()); -} - -// This string persists in configuration files -// So config compatibility will break if it is changed across Audacity versions -wxString PluginManager::GetPluginTypeString(PluginType type) -{ - wxString str; - - switch (type) - { - default: - case PluginTypeNone: - str = wxT("Placeholder"); - break; - case PluginTypeStub: - str = wxT("Stub"); - break; - case PluginTypeEffect: - str = wxT("Effect"); - break; - case PluginTypeAudacityCommand: - str = wxT("Generic"); - break; - case PluginTypeExporter: - str = wxT("Exporter"); - break; - case PluginTypeImporter: - str = wxT("Importer"); - break; - case PluginTypeModule: - str = wxT("Module"); - break; - } - - return str; -} - -PluginDescriptor & PluginManager::CreatePlugin(const PluginID & id, - ComponentInterface *ident, - PluginType type) -{ - // This will either create a NEW entry or replace an existing entry - PluginDescriptor & plug = mPlugins[id]; - - plug.SetPluginType(type); - - plug.SetID(id); - plug.SetPath(ident->GetPath()); - plug.SetSymbol(ident->GetSymbol()); - plug.SetVendor(ident->GetVendor().Internal()); - plug.SetVersion(ident->GetVersion()); - - return plug; -} - -FileConfig *PluginManager::GetSettings() -{ - if (!mSettings) - { - mSettings = - AudacityFileConfig::Create({}, {}, FileNames::PluginSettings()); - - // Check for a settings version that we can understand - if (mSettings->HasEntry(SETVERKEY)) - { - wxString setver = mSettings->Read(SETVERKEY, SETVERKEY); - if (setver < SETVERCUR ) - { - // This is where we'd put in conversion code when the - // settings version changes. - // - // Should also check for a settings file that is newer than - // what we can understand. - } - } - else - { - // Make sure is has a version string - mSettings->Write(SETVERKEY, SETVERCUR); - mSettings->Flush(); - } - } - - return mSettings.get(); -} - -bool PluginManager::HasGroup(const RegistryPath & group) -{ - auto settings = GetSettings(); - - bool res = settings->HasGroup(group); - if (res) - { - // The group exists, but empty groups aren't considered valid - wxString oldPath = settings->GetPath(); - settings->SetPath(group); - res = settings->GetNumberOfEntries() || settings->GetNumberOfGroups(); - settings->SetPath(oldPath); - } - - return res; -} - -bool PluginManager::GetSubgroups(const RegistryPath & group, RegistryPaths & subgroups) -{ - if (group.empty() || !HasGroup(group)) - { - return false; - } - - wxString path = GetSettings()->GetPath(); - GetSettings()->SetPath(group); - - wxString name; - long index = 0; - if (GetSettings()->GetFirstGroup(name, index)) - { - do - { - subgroups.push_back(name); - } while (GetSettings()->GetNextGroup(name, index)); - } - - GetSettings()->SetPath(path); - - return true; -} - -bool PluginManager::GetConfig(const RegistryPath & key, int & value, int defval) -{ - bool result = false; - - if (!key.empty()) - { - result = GetSettings()->Read(key, &value, defval); - } - - return result; -} - -bool PluginManager::GetConfig(const RegistryPath & key, wxString & value, const wxString & defval) -{ - bool result = false; - - if (!key.empty()) - { - wxString wxval; - - result = GetSettings()->Read(key, &wxval, defval); - - value = wxval; - } - - return result; -} - -bool PluginManager::GetConfig(const RegistryPath & key, bool & value, bool defval) -{ - bool result = false; - - if (!key.empty()) - { - result = GetSettings()->Read(key, &value, defval); - } - - return result; -} - -bool PluginManager::GetConfig(const RegistryPath & key, float & value, float defval) -{ - bool result = false; - - if (!key.empty()) - { - double dval = 0.0; - - result = GetSettings()->Read(key, &dval, (double) defval); - - value = (float) dval; - } - - return result; -} - -bool PluginManager::GetConfig(const RegistryPath & key, double & value, double defval) -{ - bool result = false; - - if (!key.empty()) - { - result = GetSettings()->Read(key, &value, defval); - } - - return result; -} - -bool PluginManager::SetConfig(const RegistryPath & key, const wxString & value) -{ - bool result = false; - - if (!key.empty()) - { - wxString wxval = value; - result = GetSettings()->Write(key, wxval); - if (result) - { - result = GetSettings()->Flush(); - } - } - - return result; -} - -bool PluginManager::SetConfig(const RegistryPath & key, const int & value) -{ - bool result = false; - - if (!key.empty()) - { - result = GetSettings()->Write(key, value); - if (result) - { - result = GetSettings()->Flush(); - } - } - - return result; -} - -bool PluginManager::SetConfig(const RegistryPath & key, const bool & value) -{ - bool result = false; - - if (!key.empty()) - { - result = GetSettings()->Write(key, value); - if (result) - { - result = GetSettings()->Flush(); - } - } - - return result; -} - -bool PluginManager::SetConfig(const RegistryPath & key, const float & value) -{ - bool result = false; - - if (!key.empty()) - { - result = GetSettings()->Write(key, value); - if (result) - { - result = GetSettings()->Flush(); - } - } - - return result; -} - -bool PluginManager::SetConfig(const RegistryPath & key, const double & value) -{ - bool result = false; - - if (!key.empty()) - { - result = GetSettings()->Write(key, value); - if (result) - { - result = GetSettings()->Flush(); - } - } - - return result; -} - -/* Return value is a key for lookup in a config file */ -RegistryPath PluginManager::SettingsPath(const PluginID & ID, bool shared) -{ - // All the strings reported by PluginDescriptor and used in this function - // persist in the plugin settings configuration file, so they should not - // be changed across Audacity versions, or else compatibility of the - // configuration files will break. - - if (auto iter = mPlugins.find(ID); iter == mPlugins.end()) - return {}; - else { - const PluginDescriptor & plug = iter->second; - - wxString id = GetPluginTypeString(plug.GetPluginType()) + - wxT("_") + - plug.GetEffectFamily() + // is empty for non-Effects - wxT("_") + - plug.GetVendor() + - wxT("_") + - (shared ? wxString{} : plug.GetSymbol().Internal()); - - return SETROOT + - ConvertID(id) + - wxCONFIG_PATH_SEPARATOR + - (shared ? wxT("shared") : wxT("private")) + - wxCONFIG_PATH_SEPARATOR; - } -} - -/* Return value is a key for lookup in a config file */ -RegistryPath PluginManager::SharedGroup(const PluginID & ID, const RegistryPath & group) -{ - wxString path = SettingsPath(ID, true); - - wxFileName ff(group); - if (!ff.GetName().empty()) - { - path += ff.GetFullPath(wxPATH_UNIX) + wxCONFIG_PATH_SEPARATOR; - } - - return path; -} - -/* Return value is a key for lookup in a config file */ -RegistryPath PluginManager::SharedKey(const PluginID & ID, const RegistryPath & group, const RegistryPath & key) -{ - auto path = SharedGroup(ID, group); - if (path.empty()) - { - return path; - } - - return path + key; -} - -/* Return value is a key for lookup in a config file */ -RegistryPath PluginManager::PrivateGroup(const PluginID & ID, const RegistryPath & group) -{ - auto path = SettingsPath(ID, false); - - wxFileName ff(group); - if (!ff.GetName().empty()) - { - path += ff.GetFullPath(wxPATH_UNIX) + wxCONFIG_PATH_SEPARATOR; - } - - return path; -} - -/* Return value is a key for lookup in a config file */ -RegistryPath PluginManager::PrivateKey(const PluginID & ID, const RegistryPath & group, const RegistryPath & key) -{ - auto path = PrivateGroup(ID, group); - if (path.empty()) - { - return path; - } - - return path + key; -} - -// Sanitize the ID...not the best solution, but will suffice until this -// is converted to XML. We use base64 encoding to preserve case. -wxString PluginManager::ConvertID(const PluginID & ID) -{ - if (ID.StartsWith(wxT("base64:"))) - { - wxString id = ID.Mid(7); - ArrayOf buf{ id.length() / 4 * 3 }; - id = wxString::FromUTF8(buf.get(), b64decode(id, buf.get())); - return id; - } - - const wxCharBuffer & buf = ID.ToUTF8(); - return wxT("base64:") + b64encode(buf, strlen(buf)); -} - -//////////////////////////////////////////////////////////////////////////////// -// Base64 en/decoding -// -// Original routines marked as public domain and found at: -// -// http://en.wikibooks.org/wiki/Algorithm_implementation/Miscellaneous/Base64 -// -//////////////////////////////////////////////////////////////////////////////// - -// Lookup table for encoding -const static wxChar cset[] = wxT("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"); -const static char padc = wxT('='); - -wxString PluginManager::b64encode(const void *in, int len) -{ - unsigned char *p = (unsigned char *) in; - wxString out; - - unsigned long temp; - for (int i = 0; i < len / 3; i++) - { - temp = (*p++) << 16; //Convert to big endian - temp += (*p++) << 8; - temp += (*p++); - out += cset[(temp & 0x00FC0000) >> 18]; - out += cset[(temp & 0x0003F000) >> 12]; - out += cset[(temp & 0x00000FC0) >> 6]; - out += cset[(temp & 0x0000003F)]; - } - - switch (len % 3) - { - case 1: - temp = (*p++) << 16; //Convert to big endian - out += cset[(temp & 0x00FC0000) >> 18]; - out += cset[(temp & 0x0003F000) >> 12]; - out += padc; - out += padc; - break; - - case 2: - temp = (*p++) << 16; //Convert to big endian - temp += (*p++) << 8; - out += cset[(temp & 0x00FC0000) >> 18]; - out += cset[(temp & 0x0003F000) >> 12]; - out += cset[(temp & 0x00000FC0) >> 6]; - out += padc; - break; - } - - return out; -} - -int PluginManager::b64decode(const wxString &in, void *out) -{ - int len = in.length(); - unsigned char *p = (unsigned char *) out; - - if (len % 4) //Sanity check - { - return 0; - } - - int padding = 0; - if (len) - { - if (in[len - 1] == padc) - { - padding++; - } - - if (in[len - 2] == padc) - { - padding++; - } - } - - //const char *a = in.mb_str(); - //Setup a vector to hold the result - unsigned long temp = 0; //Holds decoded quanta - int i = 0; - while (i < len) - { - for (int quantumPosition = 0; quantumPosition < 4; quantumPosition++) - { - unsigned char c = in[i]; - temp <<= 6; - - if (c >= 0x41 && c <= 0x5A) - { - temp |= c - 0x41; - } - else if (c >= 0x61 && c <= 0x7A) - { - temp |= c - 0x47; - } - else if (c >= 0x30 && c <= 0x39) - { - temp |= c + 0x04; - } - else if (c == 0x2B) - { - temp |= 0x3E; - } - else if (c == 0x2F) - { - temp |= 0x3F; - } - else if (c == padc) - { - switch (len - i) - { - case 1: //One pad character - *p++ = (temp >> 16) & 0x000000FF; - *p++ = (temp >> 8) & 0x000000FF; - return p - (unsigned char *) out; - case 2: //Two pad characters - *p++ = (temp >> 10) & 0x000000FF; - return p - (unsigned char *) out; - } - } - i++; - } - *p++ = (temp >> 16) & 0x000000FF; - *p++ = (temp >> 8) & 0x000000FF; - *p++ = temp & 0x000000FF; - } - - return p - (unsigned char *) out; -} - -// This is defined out-of-line here, to keep ComponentInterface free of other -// #include directives. -TranslatableString ComponentInterface::GetName() -{ - return GetSymbol().Msgid(); -} diff --git a/src/menus/PluginMenus.cpp b/src/menus/PluginMenus.cpp index aa8821fad..d0192a554 100644 --- a/src/menus/PluginMenus.cpp +++ b/src/menus/PluginMenus.cpp @@ -6,6 +6,7 @@ #include "../CommonCommandFlags.h" #include "../Menus.h" #include "../PluginManager.h" +#include "../PluginRegistrationDialog.h" #include "../Prefs.h" #include "../Project.h" #include "../ProjectSettings.h" @@ -36,10 +37,20 @@ AudacityProject::AttachedWindows::RegisteredFactory sMacrosWindowKey{ } }; +bool ShowManager( + PluginManager &pm, wxWindow *parent, EffectType type) +{ + pm.CheckForUpdates(); + + PluginRegistrationDialog dlg(parent, type); + return dlg.ShowModal() == wxID_OK; +} + void DoManagePluginsMenu(AudacityProject &project, EffectType type) { auto &window = GetProjectFrame( project ); - if (PluginManager::Get().ShowManager(&window, type)) + auto &pm = PluginManager::Get(); + if (ShowManager(pm, &window, type)) MenuCreator::RebuildAllMenuBars(); } From 2e7f7114d27dba8ed3b1473f892cfb2bfbbf15c0 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Wed, 23 Jun 2021 07:18:16 -0400 Subject: [PATCH 08/10] ModuleManager defines the ID strings for modules, not PluginManager --- src/ModuleManager.cpp | 16 +++++++++++++++- src/ModuleManager.h | 6 ++++++ src/PluginManager.cpp | 12 +----------- src/PluginManager.h | 1 - 4 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/ModuleManager.cpp b/src/ModuleManager.cpp index 75cf00645..a81de51a9 100755 --- a/src/ModuleManager.cpp +++ b/src/ModuleManager.cpp @@ -403,6 +403,21 @@ ModuleManager & ModuleManager::Get() return *mInstance; } +wxString ModuleManager::GetPluginTypeString() +{ + return L"Module"; +} + +PluginID ModuleManager::GetID(ModuleInterface *module) +{ + return wxString::Format(wxT("%s_%s_%s_%s_%s"), + GetPluginTypeString(), + wxEmptyString, + module->GetVendor().Internal(), + module->GetSymbol().Internal(), + module->GetPath()); +} + bool ModuleManager::DiscoverProviders() { InitializeBuiltins(); @@ -569,4 +584,3 @@ bool ModuleManager::IsPluginValid(const PluginID & providerID, return mDynModules[providerID]->IsPluginValid(path, bFast); } - diff --git a/src/ModuleManager.h b/src/ModuleManager.h index 0111eee8d..ef9d89976 100644 --- a/src/ModuleManager.h +++ b/src/ModuleManager.h @@ -77,6 +77,12 @@ public: static ModuleManager & Get(); + // This string persists in configuration files + // So config compatibility will break if it is changed across Audacity versions + static wxString GetPluginTypeString(); + + static PluginID GetID(ModuleInterface *module); + private: static void FindModules(FilePaths &files); using DelayedErrors = diff --git a/src/PluginManager.cpp b/src/PluginManager.cpp index aefe72277..beef900f9 100644 --- a/src/PluginManager.cpp +++ b/src/PluginManager.cpp @@ -1552,16 +1552,6 @@ ComponentInterface *PluginManager::GetInstance(const PluginID & ID) } } -PluginID PluginManager::GetID(ModuleInterface *module) -{ - return wxString::Format(wxT("%s_%s_%s_%s_%s"), - GetPluginTypeString(PluginTypeModule), - wxEmptyString, - module->GetVendor().Internal(), - module->GetSymbol().Internal(), - module->GetPath()); -} - PluginID PluginManager::GetID(ComponentInterface *command) { return wxString::Format(wxT("%s_%s_%s_%s_%s"), @@ -1620,7 +1610,7 @@ wxString PluginManager::GetPluginTypeString(PluginType type) str = wxT("Importer"); break; case PluginTypeModule: - str = wxT("Module"); + str = ModuleManager::GetPluginTypeString(); break; } diff --git a/src/PluginManager.h b/src/PluginManager.h index 9013cc678..e68916b3c 100644 --- a/src/PluginManager.h +++ b/src/PluginManager.h @@ -238,7 +238,6 @@ public: static PluginManager & Get(); - static PluginID GetID(ModuleInterface *module); static PluginID GetID(ComponentInterface *command); static PluginID GetID(EffectDefinitionInterface *effect); static PluginID GetID(ImporterInterface *importer); From 02b61532fe1d5de459de705ccfe81e3c8d7cda89 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Thu, 20 Feb 2020 07:35:11 -0500 Subject: [PATCH 09/10] ModuleManager doesn't use PluginManager to initialize providers --- src/ModuleManager.cpp | 23 +++-------------------- src/ModuleManager.h | 7 ++++++- src/PluginManager.cpp | 9 +++++++-- 3 files changed, 16 insertions(+), 23 deletions(-) diff --git a/src/ModuleManager.cpp b/src/ModuleManager.cpp index a81de51a9..7fa840bb7 100755 --- a/src/ModuleManager.cpp +++ b/src/ModuleManager.cpp @@ -448,20 +448,8 @@ bool ModuleManager::DiscoverProviders() FileNames::FindFilesInPathList(wxT("*.so"), pathList, provList); #endif - PluginManager & pm = PluginManager::Get(); - - for (int i = 0, cnt = provList.size(); i < cnt; i++) - { - ModuleInterface *module = LoadModule(provList[i]); - if (module) - { - // Register the provider - pm.RegisterPlugin(module); - - // Now, allow the module to auto-register children - module->AutoRegisterPlugins(pm); - } - } + for ( const auto &path : provList ) + LoadModule(path); #endif return true; @@ -469,8 +457,6 @@ bool ModuleManager::DiscoverProviders() void ModuleManager::InitializeBuiltins() { - PluginManager & pm = PluginManager::Get(); - for (auto moduleMain : builtinModuleList()) { ModuleInterfaceHandle module { @@ -481,13 +467,10 @@ void ModuleManager::InitializeBuiltins() { // Register the provider ModuleInterface *pInterface = module.get(); - const PluginID & id = pm.RegisterPlugin(pInterface); + auto id = GetID(pInterface); // Need to remember it mDynModules[id] = std::move(module); - - // Allow the module to auto-register children - pInterface->AutoRegisterPlugins(pm); } else { diff --git a/src/ModuleManager.h b/src/ModuleManager.h index ef9d89976..20197a787 100644 --- a/src/ModuleManager.h +++ b/src/ModuleManager.h @@ -12,7 +12,8 @@ #ifndef __AUDACITY_MODULEMANAGER_H__ #define __AUDACITY_MODULEMANAGER_H__ -#include +#include "MemoryX.h" +#include #include #include @@ -98,6 +99,10 @@ public: // Can be called before Initialize() bool DiscoverProviders(); + // Supports range-for iteration + auto Providers() const + { return make_iterator_range(mDynModules.cbegin(), mDynModules.cend()); } + PluginPaths FindPluginsForProvider(const PluginID & provider, const PluginPath & path); bool RegisterEffectPlugin(const PluginID & provider, const PluginPath & path, TranslatableString &errMsg); diff --git a/src/PluginManager.cpp b/src/PluginManager.cpp index beef900f9..64bf985cd 100644 --- a/src/PluginManager.cpp +++ b/src/PluginManager.cpp @@ -722,8 +722,13 @@ void PluginManager::Initialize() // And force load of setting to verify it's accessible GetSettings(); - // Then look for providers (they may autoregister plugins) - ModuleManager::Get().DiscoverProviders(); + auto &mm = ModuleManager::Get(); + mm.DiscoverProviders(); + for (const auto &[id, module] : mm.Providers()) { + RegisterPlugin(module.get()); + // Allow the module to auto-register children + module->AutoRegisterPlugins(*this); + } // And finally check for updates #ifndef EXPERIMENTAL_EFFECT_MANAGEMENT From d5a22dfb15f2396d650c38701bd804cb7390f798 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Thu, 20 Feb 2020 07:48:26 -0500 Subject: [PATCH 10/10] Eliminate last use of PluginManager by ModuleManager, breaking cycle --- src/ModuleManager.cpp | 17 ----------------- src/ModuleManager.h | 1 - src/PluginManager.cpp | 4 +++- 3 files changed, 3 insertions(+), 19 deletions(-) diff --git a/src/ModuleManager.cpp b/src/ModuleManager.cpp index 7fa840bb7..799dfdfe3 100755 --- a/src/ModuleManager.cpp +++ b/src/ModuleManager.cpp @@ -31,7 +31,6 @@ i.e. an alternative to the usual interface, for Audacity. #include "FileNames.h" #include "MemoryX.h" -#include "PluginManager.h" #include "audacity/PluginInterface.h" @@ -488,22 +487,6 @@ void ModuleInterfaceDeleter::operator() (ModuleInterface *pInterface) const } } -PluginPaths ModuleManager::FindPluginsForProvider(const PluginID & providerID, - const PluginPath & path) -{ - // Instantiate if it hasn't already been done - if (mDynModules.find(providerID) == mDynModules.end()) - { - // If it couldn't be created, just give up and return an empty list - if (!CreateProviderInstance(providerID, path)) - { - return {}; - } - } - - return mDynModules[providerID]->FindPluginPaths(PluginManager::Get()); -} - bool ModuleManager::RegisterEffectPlugin(const PluginID & providerID, const PluginPath & path, TranslatableString &errMsg) { errMsg = {}; diff --git a/src/ModuleManager.h b/src/ModuleManager.h index 20197a787..8e6107f2a 100644 --- a/src/ModuleManager.h +++ b/src/ModuleManager.h @@ -103,7 +103,6 @@ public: auto Providers() const { return make_iterator_range(mDynModules.cbegin(), mDynModules.cend()); } - PluginPaths FindPluginsForProvider(const PluginID & provider, const PluginPath & path); bool RegisterEffectPlugin(const PluginID & provider, const PluginPath & path, TranslatableString &errMsg); diff --git a/src/PluginManager.cpp b/src/PluginManager.cpp index 64bf985cd..b491ee733 100644 --- a/src/PluginManager.cpp +++ b/src/PluginManager.cpp @@ -1382,7 +1382,9 @@ void PluginManager::CheckForUpdates(bool bFast) else { // Collect plugin paths - auto paths = mm.FindPluginsForProvider(plugID, plugPath); + PluginPaths paths; + if (auto provider = mm.CreateProviderInstance( plugID, plugPath ) ) + paths = provider->FindPluginPaths( *this ); for (size_t i = 0, cnt = paths.size(); i < cnt; i++) { wxString path = paths[i].BeforeFirst(wxT(';'));;