Browse Source

Added a class KnownPluginList::PluginTree for accessing a sorted tree of plugins. Misc other improvements to plugin hosting. Also added methods for getting a plugin's VSTXML data.

tags/2021-05-28
jules 13 years ago
parent
commit
53edf99901
13 changed files with 344 additions and 270 deletions
  1. +1
    -0
      extras/audio plugin host/Source/InternalFilters.h
  2. +2
    -0
      extras/audio plugin host/Source/MainHostWindow.cpp
  3. +6
    -9
      modules/juce_audio_processors/format/juce_AudioPluginFormat.h
  4. +4
    -3
      modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.h
  5. +1
    -1
      modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm
  6. +3
    -2
      modules/juce_audio_processors/format_types/juce_DirectXPluginFormat.h
  7. +2
    -1
      modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.h
  8. +64
    -25
      modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp
  9. +10
    -5
      modules/juce_audio_processors/format_types/juce_VSTPluginFormat.h
  10. +96
    -90
      modules/juce_audio_processors/scanning/juce_KnownPluginList.cpp
  11. +16
    -9
      modules/juce_audio_processors/scanning/juce_KnownPluginList.h
  12. +122
    -118
      modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp
  13. +17
    -7
      modules/juce_audio_processors/scanning/juce_PluginListComponent.h

+ 1
- 0
extras/audio plugin host/Source/InternalFilters.h View File

@@ -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; }


+ 2
- 0
extras/audio plugin host/Source/MainHostWindow.cpp View File

@@ -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
{


+ 6
- 9
modules/juce_audio_processors/format/juce_AudioPluginFormat.h View File

@@ -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;


+ 4
- 3
modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.h View File

@@ -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:
//==============================================================================


+ 1
- 1
modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm View File

@@ -1493,7 +1493,7 @@ bool AudioUnitPluginFormat::doesPluginStillExist (const PluginDescription& desc)
FileSearchPath AudioUnitPluginFormat::getDefaultLocationsToSearch()
{
return FileSearchPath ("/(Default AudioUnit locations)");
return FileSearchPath();
}
#undef log


+ 3
- 2
modules/juce_audio_processors/format_types/juce_DirectXPluginFormat.h View File

@@ -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);


+ 2
- 1
modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.h View File

@@ -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);


+ 64
- 25
modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp View File

@@ -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

+ 10
- 5
modules/juce_audio_processors/format_types/juce_VSTPluginFormat.h View File

@@ -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:
//==============================================================================


+ 96
- 90
modules/juce_audio_processors/scanning/juce_KnownPluginList.cpp View File

@@ -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;
}

+ 16
- 9
modules/juce_audio_processors/scanning/juce_KnownPluginList.h View File

@@ -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:
//==============================================================================


+ 122
- 118
modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp View File

@@ -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 (", "));
}

+ 17
- 7
modules/juce_audio_processors/scanning/juce_PluginListComponent.h View File

@@ -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);
};


Loading…
Cancel
Save