diff --git a/extras/audio plugin host/Source/InternalFilters.h b/extras/audio plugin host/Source/InternalFilters.h index 1570e29677..ee8d90be63 100644 --- a/extras/audio plugin host/Source/InternalFilters.h +++ b/extras/audio plugin host/Source/InternalFilters.h @@ -58,6 +58,7 @@ public: String getName() const { return "Internal"; } bool fileMightContainThisPluginType (const String&) { return false; } FileSearchPath getDefaultLocationsToSearch() { return FileSearchPath(); } + bool canScanForPlugins() const { return false; } void findAllTypesForFile (OwnedArray &, const String&) {} bool doesPluginStillExist (const PluginDescription&) { return true; } String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) { return fileOrIdentifier; } diff --git a/extras/audio plugin host/Source/MainHostWindow.cpp b/extras/audio plugin host/Source/MainHostWindow.cpp index b6823e0035..c816af8d6e 100644 --- a/extras/audio plugin host/Source/MainHostWindow.cpp +++ b/extras/audio plugin host/Source/MainHostWindow.cpp @@ -257,6 +257,8 @@ void MainHostWindow::menuItemSelected (int menuItemID, int /*topLevelMenuIndex*/ else if (menuItemID == 204) pluginSortMethod = KnownPluginList::sortByFileSystemLocation; appProperties->getUserSettings()->setValue ("pluginSortMethod", (int) pluginSortMethod); + + menuItemsChanged(); } else { diff --git a/modules/juce_audio_processors/format/juce_AudioPluginFormat.h b/modules/juce_audio_processors/format/juce_AudioPluginFormat.h index 3cf9e8b2b5..5677f8cfbb 100644 --- a/modules/juce_audio_processors/format/juce_AudioPluginFormat.h +++ b/modules/juce_audio_processors/format/juce_AudioPluginFormat.h @@ -46,7 +46,6 @@ public: //============================================================================== /** Returns the format name. - E.g. "VST", "AudioUnit", etc. */ virtual String getName() const = 0; @@ -64,7 +63,6 @@ public: const String& fileOrIdentifier) = 0; /** Tries to recreate a type from a previously generated PluginDescription. - @see PluginDescription::createInstance */ virtual AudioPluginInstance* createInstanceFromDescription (const PluginDescription& desc) = 0; @@ -77,19 +75,19 @@ public: */ virtual bool fileMightContainThisPluginType (const String& fileOrIdentifier) = 0; - /** Returns a readable version of the name of the plugin that this identifier refers to. - */ + /** Returns a readable version of the name of the plugin that this identifier refers to. */ virtual String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) = 0; /** Checks whether this plugin could possibly be loaded. - It doesn't actually need to load it, just to check whether the file or component still exists. */ virtual bool doesPluginStillExist (const PluginDescription& desc) = 0; - /** Searches a suggested set of directories for any plugins in this format. + /** Returns true if this format needs to run a scan to find its list of plugins. */ + virtual bool canScanForPlugins() const = 0; + /** Searches a suggested set of directories for any plugins in this format. The path might be ignored, e.g. by AUs, which are found by the OS rather than manually. */ @@ -98,12 +96,11 @@ public: /** Returns the typical places to look for this kind of plugin. - Note that if this returns no paths, it means that the format can't be scanned-for - (i.e. it's an internal format that doesn't live in files) + Note that if this returns no paths, it means that the format doesn't search in + files or folders, e.g. AudioUnits. */ virtual FileSearchPath getDefaultLocationsToSearch() = 0; - protected: //============================================================================== AudioPluginFormat() noexcept; diff --git a/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.h b/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.h index 985c30537b..68733f0909 100644 --- a/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.h +++ b/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.h @@ -43,13 +43,14 @@ public: //============================================================================== String getName() const { return "AudioUnit"; } - void findAllTypesForFile (OwnedArray & results, const String& fileOrIdentifier); + void findAllTypesForFile (OwnedArray &, const String& fileOrIdentifier); AudioPluginInstance* createInstanceFromDescription (const PluginDescription& desc); bool fileMightContainThisPluginType (const String& fileOrIdentifier); String getNameOfPluginFromIdentifier (const String& fileOrIdentifier); - StringArray searchPathsForPlugins (const FileSearchPath& directoriesToSearch, bool recursive); - bool doesPluginStillExist (const PluginDescription& desc); + StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive); + bool doesPluginStillExist (const PluginDescription&); FileSearchPath getDefaultLocationsToSearch(); + bool canScanForPlugins() const { return true; } private: //============================================================================== diff --git a/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm b/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm index b32c3945a8..cbfea59145 100644 --- a/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm +++ b/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm @@ -1493,7 +1493,7 @@ bool AudioUnitPluginFormat::doesPluginStillExist (const PluginDescription& desc) FileSearchPath AudioUnitPluginFormat::getDefaultLocationsToSearch() { - return FileSearchPath ("/(Default AudioUnit locations)"); + return FileSearchPath(); } #undef log diff --git a/modules/juce_audio_processors/format_types/juce_DirectXPluginFormat.h b/modules/juce_audio_processors/format_types/juce_DirectXPluginFormat.h index 80d9f00008..e1092c3cba 100644 --- a/modules/juce_audio_processors/format_types/juce_DirectXPluginFormat.h +++ b/modules/juce_audio_processors/format_types/juce_DirectXPluginFormat.h @@ -47,11 +47,12 @@ public: //============================================================================== String getName() const { return "DirectX"; } - void findAllTypesForFile (OwnedArray & results, const String& fileOrIdentifier); - AudioPluginInstance* createInstanceFromDescription (const PluginDescription& desc); + void findAllTypesForFile (OwnedArray &, const String& fileOrIdentifier); + AudioPluginInstance* createInstanceFromDescription (const PluginDescription&); bool fileMightContainThisPluginType (const String& fileOrIdentifier); String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) { return fileOrIdentifier; } FileSearchPath getDefaultLocationsToSearch(); + bool canScanForPlugins() const { return true; } private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DirectXPluginFormat); diff --git a/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.h b/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.h index 6bd3a48123..01413eb906 100644 --- a/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.h +++ b/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.h @@ -48,10 +48,11 @@ public: //============================================================================== String getName() const { return "LADSPA"; } void findAllTypesForFile (OwnedArray & results, const String& fileOrIdentifier); - AudioPluginInstance* createInstanceFromDescription (const PluginDescription& desc); + AudioPluginInstance* createInstanceFromDescription (const PluginDescription&); bool fileMightContainThisPluginType (const String& fileOrIdentifier); String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) { return fileOrIdentifier; } FileSearchPath getDefaultLocationsToSearch(); + bool canScanForPlugins() const { return true; } private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LADSPAPluginFormat); diff --git a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp index 09c4175bb1..dd359a0707 100644 --- a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp +++ b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp @@ -339,6 +339,9 @@ public: File file; MainCall moduleMain; String pluginName; + ScopedPointer vstXml; + + typedef ReferenceCountedObjectPtr Ptr; static Array & getActiveModules() { @@ -428,6 +431,14 @@ public: if (moduleMain == nullptr) moduleMain = (MainCall) module.getFunction ("main"); + if (moduleMain != nullptr) + { + vstXml = XmlDocument::parse (file.withFileExtension ("vstxml")); + + if (vstXml == nullptr) + vstXml = XmlDocument::parse (getDLLResource (file, "VSTXML", 1)); + } + return moduleMain != nullptr; } @@ -443,6 +454,30 @@ public: eff->dispatcher (eff, effClose, 0, 0, 0, 0); } + static String getDLLResource (const File& dll, const char* type, int resID) + { + DynamicLibrary dll (dll.getFullPathName()); + + HMODULE dllModule = dll.getNativeHandle(); + + if (dllModule != INVALID_HANDLE_VALUE) + { + HRSRC res = FindResource (dllModule, MAKEINTRESOURCE (resID), type); + + if (res != 0) + { + HGLOBAL hGlob = LoadResource (dllModule, res); + + if (hGlob) + { + const char* data = LockResource (hGlob); + return String::fromUTF8 (data, SizeofResource (dllModule, res)); + } + } + } + + return String::empty; + } #else #if JUCE_PPC CFragConnectionID fragId; @@ -498,6 +533,14 @@ public: resFileId = CFBundleOpenBundleResourceMap (bundleRef); ok = true; + + Array vstXmlFiles; + file.getChildFile ("Contents") + .getChildFile ("Resources") + .findChildFiles (vstXmlFiles, File::findFiles, false, "*.vstxml"); + + if (vstXmlFiles.size() > 0) + vstXml = XmlDocument::parse (vstXmlFiles.getReference(0)); } } @@ -664,16 +707,11 @@ private: }; //============================================================================== -/** - An instance of a plugin, created by a VSTPluginFormat. - -*/ class VSTPluginInstance : public AudioPluginInstance, private Timer, private AsyncUpdater { public: - //============================================================================== ~VSTPluginInstance(); //============================================================================== @@ -722,8 +760,7 @@ public: void prepareToPlay (double sampleRate, int estimatedSamplesPerBlock); void releaseResources(); - void processBlock (AudioSampleBuffer& buffer, - MidiBuffer& midiMessages); + void processBlock (AudioSampleBuffer&, MidiBuffer&); bool hasEditor() const { return effect != nullptr && (effect->flags & effFlagsHasEditor) != 0; } AudioProcessorEditor* createEditor(); @@ -742,6 +779,8 @@ public: const String getParameterText (int index); bool isParameterAutomatable (int index) const; + const XmlElement* getVSTXML() const noexcept { return module != nullptr ? module->vstXml.get() : nullptr; } + //============================================================================== int getNumPrograms() { return effect != nullptr ? effect->numPrograms : 0; } int getCurrentProgram() { return dispatch (effGetProgram, 0, 0, 0, 0); } @@ -758,7 +797,7 @@ public: //============================================================================== void timerCallback(); void handleAsyncUpdate(); - VstIntPtr handleCallback (VstInt32 opcode, VstInt32 index, VstInt32 value, void *ptr, float opt); + VstIntPtr handleCallback (VstInt32, VstInt32, VstInt32, void*, float); private: //============================================================================== @@ -776,7 +815,7 @@ private: VSTMidiEventList midiEventsToSend; VstTimeInfo vstHostTime; - ReferenceCountedObjectPtr module; + ModuleHandle::Ptr module; //============================================================================== int dispatch (const int opcode, const int index, const int value, void* const ptr, float opt) const; @@ -802,12 +841,12 @@ private: void setPower (const bool on); - VSTPluginInstance (const ReferenceCountedObjectPtr & module); + VSTPluginInstance (const ModuleHandle::Ptr& module); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VSTPluginInstance); }; //============================================================================== -VSTPluginInstance::VSTPluginInstance (const ReferenceCountedObjectPtr & module_) +VSTPluginInstance::VSTPluginInstance (const ModuleHandle::Ptr& module_) : effect (nullptr), name (module_->pluginName), wantsMidiMessages (false), @@ -1005,9 +1044,7 @@ void VSTPluginInstance::processBlock (AudioSampleBuffer& buffer, MidiBuffer& mid if (initialised) { - AudioPlayHead* const playHead = getPlayHead(); - - if (playHead != nullptr) + if (AudioPlayHead* const playHead = getPlayHead()) { AudioPlayHead::CurrentPositionInfo position; playHead->getCurrentPosition (position); @@ -1242,9 +1279,7 @@ public: { if (isOpen) { - ComponentPeer* const peer = getPeer(); - - if (peer != nullptr) + if (ComponentPeer* const peer = getPeer()) { peer->addMaskedRegion (getScreenBounds() - peer->getScreenPosition()); @@ -2208,8 +2243,8 @@ namespace { String hostName ("Juce VST Host"); - if (JUCEApplication::getInstance() != nullptr) - hostName = JUCEApplication::getInstance()->getApplicationName(); + if (JUCEApplication* app = JUCEApplication::getInstance()) + hostName = app->getApplicationName(); hostName.copyToUTF8 ((char*) ptr, jmin (kVstMaxVendorStrLen, kVstMaxProductStrLen) - 1); break; @@ -2365,11 +2400,9 @@ static VstIntPtr VSTCALLBACK audioMaster (AEffect* effect, VstInt32 opcode, VstI { try { - if (effect != nullptr && effect->resvd2 != 0) - { - return ((VSTPluginInstance*)(effect->resvd2)) - ->handleCallback (opcode, index, value, ptr, opt); - } + if (effect != nullptr) + if (VSTPluginInstance* instance = (VSTPluginInstance*) (effect->resvd2)) + return instance->handleCallback (opcode, index, value, ptr, opt); return handleGeneralCallback (opcode, index, value, ptr, opt); } @@ -2822,7 +2855,7 @@ AudioPluginInstance* VSTPluginFormat::createInstanceFromDescription (const Plugi const File previousWorkingDirectory (File::getCurrentWorkingDirectory()); file.getParentDirectory().setAsCurrentWorkingDirectory(); - const ReferenceCountedObjectPtr module (ModuleHandle::findOrCreateModule (file)); + const ModuleHandle::Ptr module (ModuleHandle::findOrCreateModule (file)); if (module != nullptr) { @@ -2936,5 +2969,11 @@ FileSearchPath VSTPluginFormat::getDefaultLocationsToSearch() #endif } +const XmlElement* VSTPluginFormat::getVSTXML (AudioPluginInstance* plugin) +{ + if (VSTPluginInstance* const vst = dynamic_cast (plugin)) + return vst->getVSTXML(); +} + #endif #undef log diff --git a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.h b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.h index 53a3c54e62..2dc56fd7a7 100644 --- a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.h +++ b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.h @@ -28,7 +28,6 @@ #include "../format/juce_AudioPluginFormat.h" - #if JUCE_PLUGINHOST_VST //============================================================================== @@ -42,15 +41,21 @@ public: VSTPluginFormat(); ~VSTPluginFormat(); + /** Attempts to retreive the VSTXML data from a plugin. + Will return nullptr if the plugin isn't a VST, or if it doesn't have any VSTXML. + */ + static const XmlElement* getVSTXML (AudioPluginInstance* plugin); + //============================================================================== String getName() const { return "VST"; } - void findAllTypesForFile (OwnedArray & results, const String& fileOrIdentifier); - AudioPluginInstance* createInstanceFromDescription (const PluginDescription& desc); + void findAllTypesForFile (OwnedArray &, const String& fileOrIdentifier); + AudioPluginInstance* createInstanceFromDescription (const PluginDescription&); bool fileMightContainThisPluginType (const String& fileOrIdentifier); String getNameOfPluginFromIdentifier (const String& fileOrIdentifier); - StringArray searchPathsForPlugins (const FileSearchPath& directoriesToSearch, bool recursive); - bool doesPluginStillExist (const PluginDescription& desc); + StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive); + bool doesPluginStillExist (const PluginDescription&); FileSearchPath getDefaultLocationsToSearch(); + bool canScanForPlugins() const { return true; } private: //============================================================================== diff --git a/modules/juce_audio_processors/scanning/juce_KnownPluginList.cpp b/modules/juce_audio_processors/scanning/juce_KnownPluginList.cpp index f279a0dc24..19366afb3d 100644 --- a/modules/juce_audio_processors/scanning/juce_KnownPluginList.cpp +++ b/modules/juce_audio_processors/scanning/juce_KnownPluginList.cpp @@ -268,109 +268,138 @@ void KnownPluginList::recreateFromXml (const XmlElement& xml) } //============================================================================== -// This is used to turn a bunch of paths into a nested menu structure. -class PluginFilesystemTree +struct PluginTreeUtils { -public: - void buildTree (const Array & allPlugins) + static void buildTreeByFolder (KnownPluginList::PluginTree& tree, const Array & allPlugins) { for (int i = 0; i < allPlugins.size(); ++i) { - String path (allPlugins.getUnchecked(i) - ->fileOrIdentifier.replaceCharacter ('\\', '/') - .upToLastOccurrenceOf ("/", false, false)); + PluginDescription* const pd = allPlugins.getUnchecked (i); + + String path (pd->fileOrIdentifier.replaceCharacter ('\\', '/') + .upToLastOccurrenceOf ("/", false, false)); if (path.substring (1, 2) == ":") path = path.substring (2); - addPlugin (allPlugins.getUnchecked(i), path); + addPlugin (tree, pd, path); } - optimise(); + optimise (tree); } - void addToMenu (PopupMenu& m, const OwnedArray & allPlugins) const + static void buildTreeByCategory (KnownPluginList::PluginTree& tree, + const Array & sorted, + const KnownPluginList::SortMethod sortMethod) { - for (int i = 0; i < subFolders.size(); ++i) + String lastType; + ScopedPointer current (new KnownPluginList::PluginTree()); + + for (int i = 0; i < sorted.size(); ++i) { - const PluginFilesystemTree* const sub = subFolders.getUnchecked(i); + const PluginDescription* const pd = sorted.getUnchecked(i); + String thisType (sortMethod == KnownPluginList::sortByCategory ? pd->category + : pd->manufacturerName); - PopupMenu subMenu; - sub->addToMenu (subMenu, allPlugins); + if (! thisType.containsNonWhitespaceChars()) + thisType = "Other"; - #if JUCE_MAC - // avoid the special AU formatting nonsense on Mac.. - m.addSubMenu (sub->folder.fromFirstOccurrenceOf (":", false, false), subMenu); - #else - m.addSubMenu (sub->folder, subMenu); - #endif + if (thisType != lastType) + { + if (current->plugins.size() + current->subFolders.size() > 0) + { + current->folder = lastType; + tree.subFolders.add (current.release()); + current = new KnownPluginList::PluginTree(); + } + + lastType = thisType; + } + + current->plugins.add (pd); } - for (int i = 0; i < plugins.size(); ++i) + if (current->plugins.size() + current->subFolders.size() > 0) { - PluginDescription* const plugin = plugins.getUnchecked(i); - - m.addItem (allPlugins.indexOf (plugin) + menuIdBase, - plugin->name, true, false); + current->folder = lastType; + tree.subFolders.add (current.release()); } } -private: - String folder; - OwnedArray subFolders; - Array plugins; - - void addPlugin (PluginDescription* const pd, const String& path) + static void addPlugin (KnownPluginList::PluginTree& tree, PluginDescription* const pd, String path) { if (path.isEmpty()) { - plugins.add (pd); + tree.plugins.add (pd); } else { + #if JUCE_MAC + if (path.containsChar (':')) + path = path.fromFirstOccurrenceOf (":", false, false); // avoid the special AU formatting nonsense on Mac.. + #endif + const String firstSubFolder (path.upToFirstOccurrenceOf ("/", false, false)); - const String remainingPath (path.fromFirstOccurrenceOf ("/", false, false)); + const String remainingPath (path.fromFirstOccurrenceOf ("/", false, false)); - for (int i = subFolders.size(); --i >= 0;) + for (int i = tree.subFolders.size(); --i >= 0;) { - if (subFolders.getUnchecked(i)->folder.equalsIgnoreCase (firstSubFolder)) + KnownPluginList::PluginTree& subFolder = *tree.subFolders.getUnchecked(i); + + if (subFolder.folder.equalsIgnoreCase (firstSubFolder)) { - subFolders.getUnchecked(i)->addPlugin (pd, remainingPath); + addPlugin (subFolder, pd, remainingPath); return; } } - PluginFilesystemTree* const newFolder = new PluginFilesystemTree(); + KnownPluginList::PluginTree* const newFolder = new KnownPluginList::PluginTree(); newFolder->folder = firstSubFolder; - subFolders.add (newFolder); - - newFolder->addPlugin (pd, remainingPath); + tree.subFolders.add (newFolder); + addPlugin (*newFolder, pd, remainingPath); } } // removes any deeply nested folders that don't contain any actual plugins - void optimise() + static void optimise (KnownPluginList::PluginTree& tree) { - for (int i = subFolders.size(); --i >= 0;) + for (int i = tree.subFolders.size(); --i >= 0;) { - PluginFilesystemTree* const sub = subFolders.getUnchecked(i); + KnownPluginList::PluginTree& sub = *tree.subFolders.getUnchecked(i); + optimise (sub); - sub->optimise(); - - if (sub->plugins.size() == 0) + if (sub.plugins.size() == 0) { - for (int j = 0; j < sub->subFolders.size(); ++j) - subFolders.add (sub->subFolders.getUnchecked(j)); + for (int j = 0; j < sub.subFolders.size(); ++j) + tree.subFolders.add (sub.subFolders.getUnchecked(j)); - sub->subFolders.clear (false); - subFolders.remove (i); + sub.subFolders.clear (false); + tree.subFolders.remove (i); } } } + + static void addToMenu (const KnownPluginList::PluginTree& tree, PopupMenu& m, const OwnedArray & allPlugins) + { + for (int i = 0; i < tree.subFolders.size(); ++i) + { + const KnownPluginList::PluginTree& sub = *tree.subFolders.getUnchecked(i); + + PopupMenu subMenu; + addToMenu (sub, subMenu, allPlugins); + m.addSubMenu (sub.folder, subMenu); + } + + for (int i = 0; i < tree.plugins.size(); ++i) + { + const PluginDescription* const plugin = tree.plugins.getUnchecked(i); + + m.addItem (allPlugins.indexOf (plugin) + menuIdBase, plugin->name, true, false); + } + } }; -//============================================================================== -void KnownPluginList::addToMenu (PopupMenu& menu, const SortMethod sortMethod) const +KnownPluginList::PluginTree* KnownPluginList::createTree (const SortMethod sortMethod) const { Array sorted; @@ -381,57 +410,34 @@ void KnownPluginList::addToMenu (PopupMenu& menu, const SortMethod sortMethod) c sorted.addSorted (sorter, types.getUnchecked(i)); } - if (sortMethod == sortByCategory - || sortMethod == sortByManufacturer) - { - String lastSubMenuName; - PopupMenu sub; - - for (int i = 0; i < sorted.size(); ++i) - { - const PluginDescription* const pd = sorted.getUnchecked(i); - String thisSubMenuName (sortMethod == sortByCategory ? pd->category - : pd->manufacturerName); - - if (! thisSubMenuName.containsNonWhitespaceChars()) - thisSubMenuName = "Other"; - - if (thisSubMenuName != lastSubMenuName) - { - if (sub.getNumItems() > 0) - { - menu.addSubMenu (lastSubMenuName, sub); - sub.clear(); - } - - lastSubMenuName = thisSubMenuName; - } - - sub.addItem (types.indexOf (pd) + menuIdBase, pd->name, true, false); - } + PluginTree* tree = new PluginTree(); - if (sub.getNumItems() > 0) - menu.addSubMenu (lastSubMenuName, sub); + if (sortMethod == sortByCategory || sortMethod == sortByManufacturer) + { + PluginTreeUtils::buildTreeByCategory (*tree, sorted, sortMethod); } else if (sortMethod == sortByFileSystemLocation) { - PluginFilesystemTree root; - root.buildTree (sorted); - root.addToMenu (menu, types); + PluginTreeUtils::buildTreeByFolder (*tree, sorted); } else { for (int i = 0; i < sorted.size(); ++i) - { - const PluginDescription* const pd = sorted.getUnchecked(i); - menu.addItem (types.indexOf (pd) + menuIdBase, pd->name, true, false); - } + tree->plugins.add (sorted.getUnchecked(i)); } + + return tree; +} + +//============================================================================== +void KnownPluginList::addToMenu (PopupMenu& menu, const SortMethod sortMethod) const +{ + ScopedPointer tree (createTree (sortMethod)); + PluginTreeUtils::addToMenu (*tree, menu, types); } int KnownPluginList::getIndexChosenByMenu (const int menuResultCode) const { const int i = menuResultCode - menuIdBase; - return isPositiveAndBelow (i, types.size()) ? i : -1; } diff --git a/modules/juce_audio_processors/scanning/juce_KnownPluginList.h b/modules/juce_audio_processors/scanning/juce_KnownPluginList.h index 6958cee0b3..3085319c29 100644 --- a/modules/juce_audio_processors/scanning/juce_KnownPluginList.h +++ b/modules/juce_audio_processors/scanning/juce_KnownPluginList.h @@ -43,8 +43,7 @@ class JUCE_API KnownPluginList : public ChangeBroadcaster { public: //============================================================================== - /** Creates an empty list. - */ + /** Creates an empty list. */ KnownPluginList(); /** Destructor. */ @@ -64,8 +63,7 @@ public: */ PluginDescription* getType (int index) const noexcept { return types [index]; } - /** Looks for a type in the list which comes from this file. - */ + /** Looks for a type in the list which comes from this file. */ PluginDescription* getTypeForFile (const String& fileOrIdentifier) const; /** Looks for a type in the list which matches a plugin type ID. @@ -104,7 +102,6 @@ public: bool isListingUpToDate (const String& possiblePluginFileOrIdentifier) const; /** Scans and adds a bunch of files that might have been dragged-and-dropped. - If any types are found in the files, their descriptions are returned in the array. */ void scanAndAddDragAndDroppedFiles (AudioPluginFormatManager& formatManager, @@ -131,13 +128,10 @@ public: Use getIndexChosenByMenu() to find out the type that was chosen. */ - void addToMenu (PopupMenu& menu, - const SortMethod sortMethod) const; + void addToMenu (PopupMenu& menu, const SortMethod sortMethod) const; /** Converts a menu item index that has been chosen into its index in this list. - Returns -1 if it's not an ID that was used. - @see addToMenu */ int getIndexChosenByMenu (int menuResultCode) const; @@ -153,6 +147,19 @@ public: /** Recreates the state of this list from its stored XML format. */ void recreateFromXml (const XmlElement& xml); + //============================================================================== + /** A structure that recursively holds a tree of plugins. + @see KnownPluginList::createTree() + */ + struct PluginTree + { + String folder; /**< The name of this folder in the tree */ + OwnedArray subFolders; + Array plugins; + }; + + /** Creates a PluginTree object containing all the known plugins. */ + PluginTree* createTree (const SortMethod sortMethod) const; private: //============================================================================== diff --git a/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp b/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp index 7c58a1b9d6..a8be5b40be 100644 --- a/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp +++ b/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp @@ -23,15 +23,15 @@ ============================================================================== */ -PluginListComponent::PluginListComponent (AudioPluginFormatManager& formatManager_, +PluginListComponent::PluginListComponent (AudioPluginFormatManager& manager, KnownPluginList& listToEdit, - const File& deadMansPedalFile_, - PropertiesFile* const propertiesToUse_) - : formatManager (formatManager_), + const File& deadMansPedal, + PropertiesFile* const properties) + : formatManager (manager), list (listToEdit), - deadMansPedalFile (deadMansPedalFile_), + deadMansPedalFile (deadMansPedal), optionsButton ("Options..."), - propertiesToUse (propertiesToUse_) + propertiesToUse (properties) { listBox.setModel (this); addAndMakeVisible (&listBox); @@ -50,6 +50,12 @@ PluginListComponent::~PluginListComponent() list.removeChangeListener (this); } +void PluginListComponent::setOptionsButtonText (const String& newText) +{ + optionsButton.setButtonText (newText); + resized(); +} + void PluginListComponent::resized() { listBox.setBounds (0, 0, getWidth(), getHeight() - 30); @@ -78,9 +84,7 @@ void PluginListComponent::paintListBoxItem (int row, Graphics& g, int width, int if (rowIsSelected) g.fillAll (findColour (TextEditor::highlightColourId)); - const PluginDescription* const pd = list.getType (row); - - if (pd != nullptr) + if (const PluginDescription* const pd = list.getType (row)) { GlyphArrangement ga; ga.addCurtailedLineOfText (Font (height * 0.7f, Font::bold), pd->name, 8.0f, height * 0.8f, width - 10.0f, true); @@ -110,7 +114,9 @@ void PluginListComponent::paintListBoxItem (int row, Graphics& g, int width, int g.setColour (Colours::grey); ga.clear(); - ga.addCurtailedLineOfText (Font (height * 0.6f), desc, bb.getRight() + 10.0f, height * 0.8f, width - bb.getRight() - 12.0f, true); + ga.addCurtailedLineOfText (Font (height * 0.6f), desc, + bb.getRight() + 10.0f, height * 0.8f, + width - bb.getRight() - 12.0f, true); ga.draw (g); } } @@ -120,53 +126,28 @@ void PluginListComponent::deleteKeyPressed (int lastRowSelected) list.removeType (lastRowSelected); } -void PluginListComponent::optionsMenuCallback (int result) +void PluginListComponent::removeSelected() { - switch (result) - { - case 1: list.clear(); break; - - case 2: list.sort (KnownPluginList::sortAlphabetically); break; - case 3: list.sort (KnownPluginList::sortByCategory); break; - case 4: list.sort (KnownPluginList::sortByManufacturer); break; - - case 5: - { - const SparseSet selected (listBox.getSelectedRows()); - - for (int i = list.getNumTypes(); --i >= 0;) - if (selected.contains (i)) - list.removeType (i); - - break; - } - - case 6: - { - const PluginDescription* const desc = list.getType (listBox.getSelectedRow()); + const SparseSet selected (listBox.getSelectedRows()); - if (desc != nullptr && File (desc->fileOrIdentifier).existsAsFile()) - File (desc->fileOrIdentifier).getParentDirectory().startAsProcess(); - - break; - } - - case 7: - for (int i = list.getNumTypes(); --i >= 0;) - if (! formatManager.doesPluginStillExist (*list.getType (i))) - list.removeType (i); + for (int i = list.getNumTypes(); --i >= 0;) + if (selected.contains (i)) + list.removeType (i); +} - break; +void PluginListComponent::showSelectedFolder() +{ + const PluginDescription* const desc = list.getType (listBox.getSelectedRow()); - default: - if (result != 0) - { - typeToScan = result - 10; - startTimer (1); - } + if (desc != nullptr && File (desc->fileOrIdentifier).existsAsFile()) + File (desc->fileOrIdentifier).getParentDirectory().startAsProcess(); +} - break; - } +void PluginListComponent::removeMissingPlugins() +{ + for (int i = list.getNumTypes(); --i >= 0;) + if (! formatManager.doesPluginStillExist (*list.getType (i))) + list.removeType (i); } void PluginListComponent::optionsMenuStaticCallback (int result, PluginListComponent* pluginList) @@ -175,6 +156,23 @@ void PluginListComponent::optionsMenuStaticCallback (int result, PluginListCompo pluginList->optionsMenuCallback (result); } +void PluginListComponent::optionsMenuCallback (int result) +{ + switch (result) + { + case 0: break; + case 1: list.clear(); break; + case 2: list.sort (KnownPluginList::sortAlphabetically); break; + case 3: list.sort (KnownPluginList::sortByCategory); break; + case 4: list.sort (KnownPluginList::sortByManufacturer); break; + case 5: removeSelected(); break; + case 6: showSelectedFolder(); break; + case 7: removeMissingPlugins(); break; + + default: scanFor (formatManager.getFormat (result - 10)); break; + } +} + void PluginListComponent::buttonClicked (Button* button) { if (button == &optionsButton) @@ -194,7 +192,7 @@ void PluginListComponent::buttonClicked (Button* button) { AudioPluginFormat* const format = formatManager.getFormat (i); - if (format->getDefaultLocationsToSearch().getNumPaths() > 0) + if (format->canScanForPlugins()) menu.addItem (10 + i, "Scan for new or updated " + format->getName() + " plugins..."); } @@ -203,12 +201,6 @@ void PluginListComponent::buttonClicked (Button* button) } } -void PluginListComponent::timerCallback() -{ - stopTimer(); - scanFor (formatManager.getFormat (typeToScan)); -} - bool PluginListComponent::isInterestedInFileDrag (const StringArray& /*files*/) { return true; @@ -220,81 +212,93 @@ void PluginListComponent::filesDropped (const StringArray& files, int, int) list.scanAndAddDragAndDroppedFiles (formatManager, files, typesFound); } -void PluginListComponent::scanFor (AudioPluginFormat* format) +//============================================================================== +class PluginListComponent::Scanner : private Timer { -#if JUCE_MODAL_LOOPS_PERMITTED - if (format == nullptr) - return; - - FileSearchPath path (format->getDefaultLocationsToSearch()); - - if (propertiesToUse != nullptr) - path = propertiesToUse->getValue ("lastPluginScanPath_" + format->getName(), path.toString()); - +public: + Scanner (PluginListComponent& plc, AudioPluginFormat& format, const FileSearchPath& path) + : owner (plc), + aw (TRANS("Scanning for plugins..."), + TRANS("Searching for all possible plugin files..."), AlertWindow::NoIcon), + progress (0.0), + scanner (owner.list, format, path, true, owner.deadMansPedalFile) { - AlertWindow aw (TRANS("Select folders to scan..."), String::empty, AlertWindow::NoIcon); - FileSearchPathListComponent pathList; - pathList.setSize (500, 300); - pathList.setPath (path); - - aw.addCustomComponent (&pathList); - aw.addButton (TRANS("Scan"), 1, KeyPress (KeyPress::returnKey)); aw.addButton (TRANS("Cancel"), 0, KeyPress (KeyPress::escapeKey)); + aw.addProgressBarComponent (progress); + aw.enterModalState(); - if (aw.runModalLoop() == 0) - return; - - path = pathList.getPath(); + startTimer (20); } - if (propertiesToUse != nullptr) +private: + void timerCallback() { - propertiesToUse->setValue ("lastPluginScanPath_" + format->getName(), path.toString()); - propertiesToUse->saveIfNeeded(); - } - - double progress = 0.0; - - AlertWindow aw (TRANS("Scanning for plugins..."), - TRANS("Searching for all possible plugin files..."), AlertWindow::NoIcon); - - aw.addButton (TRANS("Cancel"), 0, KeyPress (KeyPress::escapeKey)); - aw.addProgressBarComponent (progress); - aw.enterModalState(); + aw.setMessage (TRANS("Testing:\n\n") + scanner.getNextPluginFileThatWillBeScanned()); - MessageManager::getInstance()->runDispatchLoopUntil (300); + if (scanner.scanNextFile (true) && aw.isCurrentlyModal()) + progress = scanner.getProgress(); + else + owner.scanFinished (scanner.getFailedFiles()); + } - PluginDirectoryScanner scanner (list, *format, path, true, deadMansPedalFile); + PluginListComponent& owner; + AlertWindow aw; + double progress; + PluginDirectoryScanner scanner; +}; - for (;;) +void PluginListComponent::scanFor (AudioPluginFormat* format) +{ + if (format != nullptr) { - aw.setMessage (TRANS("Testing:\n\n") + scanner.getNextPluginFileThatWillBeScanned()); + FileSearchPath path (format->getDefaultLocationsToSearch()); - MessageManager::getInstance()->runDispatchLoopUntil (50); - Timer::callPendingTimersSynchronously(); - - if (! scanner.scanNextFile (true)) - break; + if (path.getNumPaths() > 0) // if the path is empty, then paths aren't used for this format. + { + #if JUCE_MODAL_LOOPS_PERMITTED + if (propertiesToUse != nullptr) + path = propertiesToUse->getValue ("lastPluginScanPath_" + format->getName(), path.toString()); + + AlertWindow aw (TRANS("Select folders to scan..."), String::empty, AlertWindow::NoIcon); + FileSearchPathListComponent pathList; + pathList.setSize (500, 300); + pathList.setPath (path); + + aw.addCustomComponent (&pathList); + aw.addButton (TRANS("Scan"), 1, KeyPress (KeyPress::returnKey)); + aw.addButton (TRANS("Cancel"), 0, KeyPress (KeyPress::escapeKey)); + + if (aw.runModalLoop() == 0) + return; + + path = pathList.getPath(); + #else + jassertfalse; // XXX this method needs refactoring to work without modal loops.. + #endif + } - if (! aw.isCurrentlyModal()) - break; + if (propertiesToUse != nullptr) + { + propertiesToUse->setValue ("lastPluginScanPath_" + format->getName(), path.toString()); + propertiesToUse->saveIfNeeded(); + } - progress = scanner.getProgress(); + currentScanner = new Scanner (*this, *format, path); } +} - if (scanner.getFailedFiles().size() > 0) - { - StringArray shortNames; +void PluginListComponent::scanFinished (const StringArray& failedFiles) +{ + StringArray shortNames; - for (int i = 0; i < scanner.getFailedFiles().size(); ++i) - shortNames.add (File (scanner.getFailedFiles()[i]).getFileName()); + for (int i = 0; i < failedFiles.size(); ++i) + shortNames.add (File (failedFiles[i]).getFileName()); - AlertWindow::showMessageBox (AlertWindow::InfoIcon, - TRANS("Scan complete"), - TRANS("Note that the following files appeared to be plugin files, but failed to load correctly:\n\n") - + shortNames.joinIntoString (", ")); - } -#else - jassertfalse; // this method needs refactoring to work without modal loops.. -#endif + currentScanner = nullptr; // mustn't delete this before using the failed files array + + if (shortNames.size() > 0) + AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon, + TRANS("Scan complete"), + TRANS("Note that the following files appeared to be plugin files, but failed to load correctly:\n\n") + + shortNames.joinIntoString (", ")); } diff --git a/modules/juce_audio_processors/scanning/juce_PluginListComponent.h b/modules/juce_audio_processors/scanning/juce_PluginListComponent.h index 999ad1b445..5ec84a5b59 100644 --- a/modules/juce_audio_processors/scanning/juce_PluginListComponent.h +++ b/modules/juce_audio_processors/scanning/juce_PluginListComponent.h @@ -37,10 +37,9 @@ */ class JUCE_API PluginListComponent : public Component, public FileDragAndDropTarget, - public ListBoxModel, + private ListBoxModel, private ChangeListener, - private ButtonListener, // (can't use Button::Listener due to idiotic VC2005 bug) - private Timer + private ButtonListener // (can't use Button::Listener due to idiotic VC2005 bug) { public: //============================================================================== @@ -59,6 +58,9 @@ public: /** Destructor. */ ~PluginListComponent(); + /** Changes the text in the panel's button. */ + void setOptionsButtonText (const String& newText); + //============================================================================== /** @internal */ void resized(); @@ -81,16 +83,24 @@ private: ListBox listBox; TextButton optionsButton; PropertiesFile* propertiesToUse; - int typeToScan; + + class Scanner; + friend class Scanner; + friend class ScopedPointer; + ScopedPointer currentScanner; void scanFor (AudioPluginFormat*); - static void optionsMenuStaticCallback (int result, PluginListComponent*); - void optionsMenuCallback (int result); + void scanFinished (const StringArray&); + + static void optionsMenuStaticCallback (int, PluginListComponent*); + void optionsMenuCallback (int); void updateList(); + void removeSelected(); + void showSelectedFolder(); + void removeMissingPlugins(); void buttonClicked (Button*); void changeListenerCallback (ChangeBroadcaster*); - void timerCallback(); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PluginListComponent); };