| @@ -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 <PluginDescription>&, const String&) {} | |||
| bool doesPluginStillExist (const PluginDescription&) { return true; } | |||
| String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) { return fileOrIdentifier; } | |||
| @@ -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 | |||
| { | |||
| @@ -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; | |||
| @@ -43,13 +43,14 @@ public: | |||
| //============================================================================== | |||
| String getName() const { return "AudioUnit"; } | |||
| void findAllTypesForFile (OwnedArray <PluginDescription>& results, const String& fileOrIdentifier); | |||
| void findAllTypesForFile (OwnedArray <PluginDescription>&, 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: | |||
| //============================================================================== | |||
| @@ -1493,7 +1493,7 @@ bool AudioUnitPluginFormat::doesPluginStillExist (const PluginDescription& desc) | |||
| FileSearchPath AudioUnitPluginFormat::getDefaultLocationsToSearch() | |||
| { | |||
| return FileSearchPath ("/(Default AudioUnit locations)"); | |||
| return FileSearchPath(); | |||
| } | |||
| #undef log | |||
| @@ -47,11 +47,12 @@ public: | |||
| //============================================================================== | |||
| String getName() const { return "DirectX"; } | |||
| void findAllTypesForFile (OwnedArray <PluginDescription>& results, const String& fileOrIdentifier); | |||
| AudioPluginInstance* createInstanceFromDescription (const PluginDescription& desc); | |||
| void findAllTypesForFile (OwnedArray <PluginDescription>&, 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); | |||
| @@ -48,10 +48,11 @@ public: | |||
| //============================================================================== | |||
| String getName() const { return "LADSPA"; } | |||
| void findAllTypesForFile (OwnedArray <PluginDescription>& 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); | |||
| @@ -339,6 +339,9 @@ public: | |||
| File file; | |||
| MainCall moduleMain; | |||
| String pluginName; | |||
| ScopedPointer<XmlElement> vstXml; | |||
| typedef ReferenceCountedObjectPtr<ModuleHandle> Ptr; | |||
| static Array <ModuleHandle*>& 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<File> 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 <ModuleHandle> 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 <ModuleHandle>& module); | |||
| VSTPluginInstance (const ModuleHandle::Ptr& module); | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VSTPluginInstance); | |||
| }; | |||
| //============================================================================== | |||
| VSTPluginInstance::VSTPluginInstance (const ReferenceCountedObjectPtr <ModuleHandle>& 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 <ModuleHandle> 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 <VSTPluginInstance*> (plugin)) | |||
| return vst->getVSTXML(); | |||
| } | |||
| #endif | |||
| #undef log | |||
| @@ -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 <PluginDescription>& results, const String& fileOrIdentifier); | |||
| AudioPluginInstance* createInstanceFromDescription (const PluginDescription& desc); | |||
| void findAllTypesForFile (OwnedArray <PluginDescription>&, 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: | |||
| //============================================================================== | |||
| @@ -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 <PluginDescription*>& allPlugins) | |||
| static void buildTreeByFolder (KnownPluginList::PluginTree& tree, const Array <PluginDescription*>& 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 <PluginDescription>& allPlugins) const | |||
| static void buildTreeByCategory (KnownPluginList::PluginTree& tree, | |||
| const Array <PluginDescription*>& sorted, | |||
| const KnownPluginList::SortMethod sortMethod) | |||
| { | |||
| for (int i = 0; i < subFolders.size(); ++i) | |||
| String lastType; | |||
| ScopedPointer<KnownPluginList::PluginTree> 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 <PluginFilesystemTree> subFolders; | |||
| Array <PluginDescription*> 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 <PluginDescription>& 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 <PluginDescription*> 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<PluginTree> 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; | |||
| } | |||
| @@ -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 <PluginTree> subFolders; | |||
| Array <const PluginDescription*> plugins; | |||
| }; | |||
| /** Creates a PluginTree object containing all the known plugins. */ | |||
| PluginTree* createTree (const SortMethod sortMethod) const; | |||
| private: | |||
| //============================================================================== | |||
| @@ -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 <int> 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 <int> 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 (", ")); | |||
| } | |||
| @@ -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<Scanner>; | |||
| ScopedPointer<Scanner> 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); | |||
| }; | |||