git subrepo clone --branch=sono6good https://github.com/essej/JUCE.git deps/juce
subrepo: subdir: "deps/juce" merged: "b13f9084e" upstream: origin: "https://github.com/essej/JUCE.git" branch: "sono6good" commit: "b13f9084e" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo.git" commit: "2f68596"
This commit is contained in:
642
deps/juce/modules/juce_audio_processors/scanning/juce_KnownPluginList.cpp
vendored
Normal file
642
deps/juce/modules/juce_audio_processors/scanning/juce_KnownPluginList.cpp
vendored
Normal file
@ -0,0 +1,642 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
KnownPluginList::KnownPluginList() {}
|
||||
KnownPluginList::~KnownPluginList() {}
|
||||
|
||||
void KnownPluginList::clear()
|
||||
{
|
||||
ScopedLock lock (typesArrayLock);
|
||||
|
||||
if (! types.isEmpty())
|
||||
{
|
||||
types.clear();
|
||||
sendChangeMessage();
|
||||
}
|
||||
}
|
||||
|
||||
int KnownPluginList::getNumTypes() const noexcept
|
||||
{
|
||||
ScopedLock lock (typesArrayLock);
|
||||
return types.size();
|
||||
}
|
||||
|
||||
Array<PluginDescription> KnownPluginList::getTypes() const
|
||||
{
|
||||
ScopedLock lock (typesArrayLock);
|
||||
return types;
|
||||
}
|
||||
|
||||
Array<PluginDescription> KnownPluginList::getTypesForFormat (AudioPluginFormat& format) const
|
||||
{
|
||||
Array<PluginDescription> result;
|
||||
|
||||
for (auto& d : getTypes())
|
||||
if (d.pluginFormatName == format.getName())
|
||||
result.add (d);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::unique_ptr<PluginDescription> KnownPluginList::getTypeForFile (const String& fileOrIdentifier) const
|
||||
{
|
||||
ScopedLock lock (typesArrayLock);
|
||||
|
||||
for (auto& desc : types)
|
||||
if (desc.fileOrIdentifier == fileOrIdentifier)
|
||||
return std::make_unique<PluginDescription> (desc);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::unique_ptr<PluginDescription> KnownPluginList::getTypeForIdentifierString (const String& identifierString) const
|
||||
{
|
||||
ScopedLock lock (typesArrayLock);
|
||||
|
||||
for (auto& desc : types)
|
||||
if (desc.matchesIdentifierString (identifierString))
|
||||
return std::make_unique<PluginDescription> (desc);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool KnownPluginList::addType (const PluginDescription& type)
|
||||
{
|
||||
{
|
||||
ScopedLock lock (typesArrayLock);
|
||||
|
||||
for (auto& desc : types)
|
||||
{
|
||||
if (desc.isDuplicateOf (type))
|
||||
{
|
||||
// strange - found a duplicate plugin with different info..
|
||||
jassert (desc.name == type.name);
|
||||
jassert (desc.isInstrument == type.isInstrument);
|
||||
|
||||
desc = type;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
types.insert (0, type);
|
||||
}
|
||||
|
||||
sendChangeMessage();
|
||||
return true;
|
||||
}
|
||||
|
||||
void KnownPluginList::removeType (const PluginDescription& type)
|
||||
{
|
||||
{
|
||||
ScopedLock lock (typesArrayLock);
|
||||
|
||||
for (int i = types.size(); --i >= 0;)
|
||||
if (types.getUnchecked (i).isDuplicateOf (type))
|
||||
types.remove (i);
|
||||
}
|
||||
|
||||
sendChangeMessage();
|
||||
}
|
||||
|
||||
bool KnownPluginList::isListingUpToDate (const String& fileOrIdentifier,
|
||||
AudioPluginFormat& formatToUse) const
|
||||
{
|
||||
if (getTypeForFile (fileOrIdentifier) == nullptr)
|
||||
return false;
|
||||
|
||||
ScopedLock lock (typesArrayLock);
|
||||
|
||||
for (auto& d : types)
|
||||
if (d.fileOrIdentifier == fileOrIdentifier && formatToUse.pluginNeedsRescanning (d))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void KnownPluginList::setCustomScanner (std::unique_ptr<CustomScanner> newScanner)
|
||||
{
|
||||
if (scanner != newScanner)
|
||||
scanner = std::move (newScanner);
|
||||
}
|
||||
|
||||
bool KnownPluginList::scanAndAddFile (const String& fileOrIdentifier,
|
||||
const bool dontRescanIfAlreadyInList,
|
||||
OwnedArray<PluginDescription>& typesFound,
|
||||
AudioPluginFormat& format)
|
||||
{
|
||||
const ScopedLock sl (scanLock);
|
||||
|
||||
if (dontRescanIfAlreadyInList
|
||||
&& getTypeForFile (fileOrIdentifier) != nullptr)
|
||||
{
|
||||
bool needsRescanning = false;
|
||||
|
||||
ScopedLock lock (typesArrayLock);
|
||||
|
||||
for (auto& d : types)
|
||||
{
|
||||
if (d.fileOrIdentifier == fileOrIdentifier && d.pluginFormatName == format.getName())
|
||||
{
|
||||
if (format.pluginNeedsRescanning (d))
|
||||
needsRescanning = true;
|
||||
else
|
||||
typesFound.add (new PluginDescription (d));
|
||||
}
|
||||
}
|
||||
|
||||
if (! needsRescanning)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (blacklist.contains (fileOrIdentifier))
|
||||
return false;
|
||||
|
||||
OwnedArray<PluginDescription> found;
|
||||
|
||||
{
|
||||
const ScopedUnlock sl2 (scanLock);
|
||||
|
||||
if (scanner != nullptr)
|
||||
{
|
||||
if (! scanner->findPluginTypesFor (format, found, fileOrIdentifier))
|
||||
addToBlacklist (fileOrIdentifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
format.findAllTypesForFile (found, fileOrIdentifier);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto* desc : found)
|
||||
{
|
||||
if (desc == nullptr)
|
||||
{
|
||||
jassertfalse;
|
||||
continue;
|
||||
}
|
||||
|
||||
addType (*desc);
|
||||
typesFound.add (new PluginDescription (*desc));
|
||||
}
|
||||
|
||||
return ! found.isEmpty();
|
||||
}
|
||||
|
||||
void KnownPluginList::scanAndAddDragAndDroppedFiles (AudioPluginFormatManager& formatManager,
|
||||
const StringArray& files,
|
||||
OwnedArray<PluginDescription>& typesFound)
|
||||
{
|
||||
for (const auto& filenameOrID : files)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
for (auto format : formatManager.getFormats())
|
||||
{
|
||||
if (format->fileMightContainThisPluginType (filenameOrID)
|
||||
&& scanAndAddFile (filenameOrID, true, typesFound, *format))
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! found)
|
||||
{
|
||||
File f (filenameOrID);
|
||||
|
||||
if (f.isDirectory())
|
||||
{
|
||||
StringArray s;
|
||||
|
||||
for (auto& subFile : f.findChildFiles (File::findFilesAndDirectories, false))
|
||||
s.add (subFile.getFullPathName());
|
||||
|
||||
scanAndAddDragAndDroppedFiles (formatManager, s, typesFound);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scanFinished();
|
||||
}
|
||||
|
||||
void KnownPluginList::scanFinished()
|
||||
{
|
||||
if (scanner != nullptr)
|
||||
scanner->scanFinished();
|
||||
}
|
||||
|
||||
const StringArray& KnownPluginList::getBlacklistedFiles() const
|
||||
{
|
||||
return blacklist;
|
||||
}
|
||||
|
||||
void KnownPluginList::addToBlacklist (const String& pluginID)
|
||||
{
|
||||
if (! blacklist.contains (pluginID))
|
||||
{
|
||||
blacklist.add (pluginID);
|
||||
sendChangeMessage();
|
||||
}
|
||||
}
|
||||
|
||||
void KnownPluginList::removeFromBlacklist (const String& pluginID)
|
||||
{
|
||||
const int index = blacklist.indexOf (pluginID);
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
blacklist.remove (index);
|
||||
sendChangeMessage();
|
||||
}
|
||||
}
|
||||
|
||||
void KnownPluginList::clearBlacklistedFiles()
|
||||
{
|
||||
if (blacklist.size() > 0)
|
||||
{
|
||||
blacklist.clear();
|
||||
sendChangeMessage();
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct PluginSorter
|
||||
{
|
||||
PluginSorter (KnownPluginList::SortMethod sortMethod, bool forwards) noexcept
|
||||
: method (sortMethod), direction (forwards ? 1 : -1) {}
|
||||
|
||||
bool operator() (const PluginDescription& first, const PluginDescription& second) const
|
||||
{
|
||||
int diff = 0;
|
||||
|
||||
switch (method)
|
||||
{
|
||||
case KnownPluginList::sortByCategory: diff = first.category.compareNatural (second.category, false); break;
|
||||
case KnownPluginList::sortByManufacturer: diff = first.manufacturerName.compareNatural (second.manufacturerName, false); break;
|
||||
case KnownPluginList::sortByFormat: diff = first.pluginFormatName.compare (second.pluginFormatName); break;
|
||||
case KnownPluginList::sortByFileSystemLocation: diff = lastPathPart (first.fileOrIdentifier).compare (lastPathPart (second.fileOrIdentifier)); break;
|
||||
case KnownPluginList::sortByInfoUpdateTime: diff = compare (first.lastInfoUpdateTime, second.lastInfoUpdateTime); break;
|
||||
case KnownPluginList::sortAlphabetically:
|
||||
case KnownPluginList::defaultOrder:
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (diff == 0)
|
||||
diff = first.name.compareNatural (second.name, false);
|
||||
|
||||
return diff * direction < 0;
|
||||
}
|
||||
|
||||
private:
|
||||
static String lastPathPart (const String& path)
|
||||
{
|
||||
return path.replaceCharacter ('\\', '/').upToLastOccurrenceOf ("/", false, false);
|
||||
}
|
||||
|
||||
static int compare (Time a, Time b) noexcept
|
||||
{
|
||||
if (a < b) return -1;
|
||||
if (b < a) return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
KnownPluginList::SortMethod method;
|
||||
int direction;
|
||||
};
|
||||
|
||||
void KnownPluginList::sort (const SortMethod method, bool forwards)
|
||||
{
|
||||
if (method != defaultOrder)
|
||||
{
|
||||
Array<PluginDescription> oldOrder, newOrder;
|
||||
|
||||
{
|
||||
ScopedLock lock (typesArrayLock);
|
||||
|
||||
oldOrder.addArray (types);
|
||||
std::stable_sort (types.begin(), types.end(), PluginSorter (method, forwards));
|
||||
newOrder.addArray (types);
|
||||
}
|
||||
|
||||
auto hasOrderChanged = [&]
|
||||
{
|
||||
for (int i = 0; i < oldOrder.size(); ++i)
|
||||
if (! oldOrder[i].isDuplicateOf (newOrder[i]))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}();
|
||||
|
||||
if (hasOrderChanged)
|
||||
sendChangeMessage();
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<XmlElement> KnownPluginList::createXml() const
|
||||
{
|
||||
auto e = std::make_unique<XmlElement> ("KNOWNPLUGINS");
|
||||
|
||||
{
|
||||
ScopedLock lock (typesArrayLock);
|
||||
|
||||
for (int i = types.size(); --i >= 0;)
|
||||
e->prependChildElement (types.getUnchecked (i).createXml().release());
|
||||
}
|
||||
|
||||
for (auto& b : blacklist)
|
||||
e->createNewChildElement ("BLACKLISTED")->setAttribute ("id", b);
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
void KnownPluginList::recreateFromXml (const XmlElement& xml)
|
||||
{
|
||||
clear();
|
||||
clearBlacklistedFiles();
|
||||
|
||||
if (xml.hasTagName ("KNOWNPLUGINS"))
|
||||
{
|
||||
for (auto* e : xml.getChildIterator())
|
||||
{
|
||||
PluginDescription info;
|
||||
|
||||
if (e->hasTagName ("BLACKLISTED"))
|
||||
blacklist.add (e->getStringAttribute ("id"));
|
||||
else if (info.loadFromXml (*e))
|
||||
addType (info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct PluginTreeUtils
|
||||
{
|
||||
enum { menuIdBase = 0x324503f4 };
|
||||
|
||||
static void buildTreeByFolder (KnownPluginList::PluginTree& tree, const Array<PluginDescription>& allPlugins)
|
||||
{
|
||||
for (auto& pd : allPlugins)
|
||||
{
|
||||
auto path = pd.fileOrIdentifier.replaceCharacter ('\\', '/')
|
||||
.upToLastOccurrenceOf ("/", false, false);
|
||||
|
||||
if (path.substring (1, 2) == ":")
|
||||
path = path.substring (2);
|
||||
|
||||
addPlugin (tree, pd, path);
|
||||
}
|
||||
|
||||
optimiseFolders (tree, false);
|
||||
}
|
||||
|
||||
static void optimiseFolders (KnownPluginList::PluginTree& tree, bool concatenateName)
|
||||
{
|
||||
for (int i = tree.subFolders.size(); --i >= 0;)
|
||||
{
|
||||
auto& sub = *tree.subFolders.getUnchecked(i);
|
||||
optimiseFolders (sub, concatenateName || (tree.subFolders.size() > 1));
|
||||
|
||||
if (sub.plugins.isEmpty())
|
||||
{
|
||||
for (auto* s : sub.subFolders)
|
||||
{
|
||||
if (concatenateName)
|
||||
s->folder = sub.folder + "/" + s->folder;
|
||||
|
||||
tree.subFolders.add (s);
|
||||
}
|
||||
|
||||
sub.subFolders.clear (false);
|
||||
tree.subFolders.remove (i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void buildTreeByCategory (KnownPluginList::PluginTree& tree,
|
||||
const Array<PluginDescription>& sorted,
|
||||
const KnownPluginList::SortMethod sortMethod)
|
||||
{
|
||||
String lastType;
|
||||
auto current = std::make_unique<KnownPluginList::PluginTree>();
|
||||
|
||||
for (auto& pd : sorted)
|
||||
{
|
||||
auto thisType = (sortMethod == KnownPluginList::sortByCategory ? pd.category
|
||||
: pd.manufacturerName);
|
||||
|
||||
if (! thisType.containsNonWhitespaceChars())
|
||||
thisType = "Other";
|
||||
|
||||
if (! thisType.equalsIgnoreCase (lastType))
|
||||
{
|
||||
if (current->plugins.size() + current->subFolders.size() > 0)
|
||||
{
|
||||
current->folder = lastType;
|
||||
tree.subFolders.add (std::move (current));
|
||||
current = std::make_unique<KnownPluginList::PluginTree>();
|
||||
}
|
||||
|
||||
lastType = thisType;
|
||||
}
|
||||
|
||||
current->plugins.add (pd);
|
||||
}
|
||||
|
||||
if (current->plugins.size() + current->subFolders.size() > 0)
|
||||
{
|
||||
current->folder = lastType;
|
||||
tree.subFolders.add (std::move (current));
|
||||
}
|
||||
}
|
||||
|
||||
static void addPlugin (KnownPluginList::PluginTree& tree, PluginDescription pd, String path)
|
||||
{
|
||||
#if JUCE_MAC
|
||||
if (path.containsChar (':'))
|
||||
path = path.fromFirstOccurrenceOf (":", false, false); // avoid the special AU formatting nonsense on Mac..
|
||||
#endif
|
||||
|
||||
if (path.isEmpty())
|
||||
{
|
||||
tree.plugins.add (pd);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto firstSubFolder = path.upToFirstOccurrenceOf ("/", false, false);
|
||||
auto remainingPath = path.fromFirstOccurrenceOf ("/", false, false);
|
||||
|
||||
for (int i = tree.subFolders.size(); --i >= 0;)
|
||||
{
|
||||
auto& subFolder = *tree.subFolders.getUnchecked (i);
|
||||
|
||||
if (subFolder.folder.equalsIgnoreCase (firstSubFolder))
|
||||
{
|
||||
addPlugin (subFolder, pd, remainingPath);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto* newFolder = new KnownPluginList::PluginTree();
|
||||
newFolder->folder = firstSubFolder;
|
||||
tree.subFolders.add (newFolder);
|
||||
addPlugin (*newFolder, pd, remainingPath);
|
||||
}
|
||||
}
|
||||
|
||||
static bool containsDuplicateNames (const Array<PluginDescription>& plugins, const String& name)
|
||||
{
|
||||
int matches = 0;
|
||||
|
||||
for (auto& p : plugins)
|
||||
if (p.name == name && ++matches > 1)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool addToMenu (const KnownPluginList::PluginTree& tree, PopupMenu& m,
|
||||
const Array<PluginDescription>& allPlugins,
|
||||
const String& currentlyTickedPluginID)
|
||||
{
|
||||
bool isTicked = false;
|
||||
|
||||
for (auto* sub : tree.subFolders)
|
||||
{
|
||||
PopupMenu subMenu;
|
||||
auto isItemTicked = addToMenu (*sub, subMenu, allPlugins, currentlyTickedPluginID);
|
||||
isTicked = isTicked || isItemTicked;
|
||||
|
||||
m.addSubMenu (sub->folder, subMenu, true, nullptr, isItemTicked, 0);
|
||||
}
|
||||
|
||||
auto getPluginMenuIndex = [&] (const PluginDescription& d)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
for (auto& p : allPlugins)
|
||||
{
|
||||
if (p.isDuplicateOf (d))
|
||||
return i + menuIdBase;
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
for (auto& plugin : tree.plugins)
|
||||
{
|
||||
auto name = plugin.name;
|
||||
|
||||
if (containsDuplicateNames (tree.plugins, name))
|
||||
name << " (" << plugin.pluginFormatName << ')';
|
||||
|
||||
auto isItemTicked = plugin.matchesIdentifierString (currentlyTickedPluginID);
|
||||
isTicked = isTicked || isItemTicked;
|
||||
|
||||
m.addItem (getPluginMenuIndex (plugin), name, true, isItemTicked);
|
||||
}
|
||||
|
||||
return isTicked;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<KnownPluginList::PluginTree> KnownPluginList::createTree (const Array<PluginDescription>& types, SortMethod sortMethod)
|
||||
{
|
||||
Array<PluginDescription> sorted;
|
||||
sorted.addArray (types);
|
||||
|
||||
std::stable_sort (sorted.begin(), sorted.end(), PluginSorter (sortMethod, true));
|
||||
|
||||
auto tree = std::make_unique<PluginTree>();
|
||||
|
||||
if (sortMethod == sortByCategory || sortMethod == sortByManufacturer || sortMethod == sortByFormat)
|
||||
{
|
||||
PluginTreeUtils::buildTreeByCategory (*tree, sorted, sortMethod);
|
||||
}
|
||||
else if (sortMethod == sortByFileSystemLocation)
|
||||
{
|
||||
PluginTreeUtils::buildTreeByFolder (*tree, sorted);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto& p : sorted)
|
||||
tree->plugins.add (p);
|
||||
}
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void KnownPluginList::addToMenu (PopupMenu& menu, const Array<PluginDescription>& types, SortMethod sortMethod,
|
||||
const String& currentlyTickedPluginID)
|
||||
{
|
||||
auto tree = createTree (types, sortMethod);
|
||||
PluginTreeUtils::addToMenu (*tree, menu, types, currentlyTickedPluginID);
|
||||
}
|
||||
|
||||
int KnownPluginList::getIndexChosenByMenu (const Array<PluginDescription>& types, int menuResultCode)
|
||||
{
|
||||
auto i = menuResultCode - PluginTreeUtils::menuIdBase;
|
||||
return isPositiveAndBelow (i, types.size()) ? i : -1;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
KnownPluginList::CustomScanner::CustomScanner() {}
|
||||
KnownPluginList::CustomScanner::~CustomScanner() {}
|
||||
|
||||
void KnownPluginList::CustomScanner::scanFinished() {}
|
||||
|
||||
bool KnownPluginList::CustomScanner::shouldExit() const noexcept
|
||||
{
|
||||
if (auto* job = ThreadPoolJob::getCurrentThreadPoolJob())
|
||||
return job->shouldExit();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void KnownPluginList::addToMenu (PopupMenu& menu, SortMethod sortMethod, const String& currentlyTickedPluginID) const
|
||||
{
|
||||
addToMenu (menu, getTypes(), sortMethod, currentlyTickedPluginID);
|
||||
}
|
||||
|
||||
int KnownPluginList::getIndexChosenByMenu (int menuResultCode) const
|
||||
{
|
||||
return getIndexChosenByMenu (getTypes(), menuResultCode);
|
||||
}
|
||||
|
||||
std::unique_ptr<KnownPluginList::PluginTree> KnownPluginList::createTree (const SortMethod sortMethod) const
|
||||
{
|
||||
return createTree (getTypes(), sortMethod);
|
||||
}
|
||||
|
||||
|
||||
} // namespace juce
|
240
deps/juce/modules/juce_audio_processors/scanning/juce_KnownPluginList.h
vendored
Normal file
240
deps/juce/modules/juce_audio_processors/scanning/juce_KnownPluginList.h
vendored
Normal file
@ -0,0 +1,240 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Manages a list of plugin types.
|
||||
|
||||
This can be easily edited, saved and loaded, and used to create instances of
|
||||
the plugin types in it.
|
||||
|
||||
@see PluginListComponent
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class JUCE_API KnownPluginList : public ChangeBroadcaster
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an empty list. */
|
||||
KnownPluginList();
|
||||
|
||||
/** Destructor. */
|
||||
~KnownPluginList() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Clears the list. */
|
||||
void clear();
|
||||
|
||||
/** Adds a type manually from its description. */
|
||||
bool addType (const PluginDescription& type);
|
||||
|
||||
/** Removes a type. */
|
||||
void removeType (const PluginDescription& type);
|
||||
|
||||
/** Returns the number of types currently in the list. */
|
||||
int getNumTypes() const noexcept;
|
||||
|
||||
/** Returns a copy of the current list. */
|
||||
Array<PluginDescription> getTypes() const;
|
||||
|
||||
/** Returns the subset of plugin types for a given format. */
|
||||
Array<PluginDescription> getTypesForFormat (AudioPluginFormat&) const;
|
||||
|
||||
/** Looks for a type in the list which comes from this file. */
|
||||
std::unique_ptr<PluginDescription> getTypeForFile (const String& fileOrIdentifier) const;
|
||||
|
||||
/** Looks for a type in the list which matches a plugin type ID.
|
||||
|
||||
The identifierString parameter must have been created by
|
||||
PluginDescription::createIdentifierString().
|
||||
*/
|
||||
std::unique_ptr<PluginDescription> getTypeForIdentifierString (const String& identifierString) const;
|
||||
|
||||
/** Looks for all types that can be loaded from a given file, and adds them
|
||||
to the list.
|
||||
|
||||
If dontRescanIfAlreadyInList is true, then the file will only be loaded and
|
||||
re-tested if it's not already in the list, or if the file's modification
|
||||
time has changed since the list was created. If dontRescanIfAlreadyInList is
|
||||
false, the file will always be reloaded and tested.
|
||||
|
||||
Returns true if any new types were added, and all the types found in this
|
||||
file (even if it was already known and hasn't been re-scanned) get returned
|
||||
in the array.
|
||||
*/
|
||||
bool scanAndAddFile (const String& possiblePluginFileOrIdentifier,
|
||||
bool dontRescanIfAlreadyInList,
|
||||
OwnedArray<PluginDescription>& typesFound,
|
||||
AudioPluginFormat& formatToUse);
|
||||
|
||||
/** Tells a custom scanner that a scan has finished, and it can release any resources. */
|
||||
void scanFinished();
|
||||
|
||||
/** Returns true if the specified file is already known about and if it
|
||||
hasn't been modified since our entry was created.
|
||||
*/
|
||||
bool isListingUpToDate (const String& possiblePluginFileOrIdentifier,
|
||||
AudioPluginFormat& formatToUse) 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,
|
||||
const StringArray& filenames,
|
||||
OwnedArray<PluginDescription>& typesFound);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the list of blacklisted files. */
|
||||
const StringArray& getBlacklistedFiles() const;
|
||||
|
||||
/** Adds a plugin ID to the black-list. */
|
||||
void addToBlacklist (const String& pluginID);
|
||||
|
||||
/** Removes a plugin ID from the black-list. */
|
||||
void removeFromBlacklist (const String& pluginID);
|
||||
|
||||
/** Clears all the blacklisted files. */
|
||||
void clearBlacklistedFiles();
|
||||
|
||||
//==============================================================================
|
||||
/** Sort methods used to change the order of the plugins in the list.
|
||||
*/
|
||||
enum SortMethod
|
||||
{
|
||||
defaultOrder = 0,
|
||||
sortAlphabetically,
|
||||
sortByCategory,
|
||||
sortByManufacturer,
|
||||
sortByFormat,
|
||||
sortByFileSystemLocation,
|
||||
sortByInfoUpdateTime
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Adds the plug-in types to a popup menu so that the user can select one.
|
||||
|
||||
Depending on the sort method, it may add sub-menus for categories,
|
||||
manufacturers, etc.
|
||||
|
||||
Use getIndexChosenByMenu() to find out the type that was chosen.
|
||||
*/
|
||||
static void addToMenu (PopupMenu& menu, const Array<PluginDescription>& types,
|
||||
SortMethod sortMethod, const String& currentlyTickedPluginID = {});
|
||||
|
||||
/** Converts a menu item index that has been chosen into its index in the list.
|
||||
Returns -1 if it's not an ID that was used.
|
||||
@see addToMenu
|
||||
*/
|
||||
static int getIndexChosenByMenu (const Array<PluginDescription>& types, int menuResultCode);
|
||||
|
||||
//==============================================================================
|
||||
/** Sorts the list. */
|
||||
void sort (SortMethod method, bool forwards);
|
||||
|
||||
//==============================================================================
|
||||
/** Creates some XML that can be used to store the state of this list. */
|
||||
std::unique_ptr<XmlElement> createXml() const;
|
||||
|
||||
/** 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<PluginDescription> plugins;
|
||||
};
|
||||
|
||||
/** Creates a PluginTree object representing the list of plug-ins. */
|
||||
static std::unique_ptr<PluginTree> createTree (const Array<PluginDescription>& types, SortMethod sortMethod);
|
||||
|
||||
//==============================================================================
|
||||
/** Class to define a custom plugin scanner */
|
||||
class CustomScanner
|
||||
{
|
||||
public:
|
||||
CustomScanner();
|
||||
virtual ~CustomScanner();
|
||||
|
||||
/** Attempts to load the given file and find a list of plugins in it.
|
||||
@returns true if the plugin loaded, false if it crashed
|
||||
*/
|
||||
virtual bool findPluginTypesFor (AudioPluginFormat& format,
|
||||
OwnedArray<PluginDescription>& result,
|
||||
const String& fileOrIdentifier) = 0;
|
||||
|
||||
/** Called when a scan has finished, to allow clean-up of resources. */
|
||||
virtual void scanFinished();
|
||||
|
||||
/** Returns true if the current scan should be abandoned.
|
||||
Any blocking methods should check this value repeatedly and return if
|
||||
if becomes true.
|
||||
*/
|
||||
bool shouldExit() const noexcept;
|
||||
};
|
||||
|
||||
/** Supplies a custom scanner to be used in future scans.
|
||||
The KnownPluginList will take ownership of the object passed in.
|
||||
*/
|
||||
void setCustomScanner (std::unique_ptr<CustomScanner> newScanner);
|
||||
|
||||
//==============================================================================
|
||||
#ifndef DOXYGEN
|
||||
// These methods have been deprecated! When getting the list of plugin types you should instead use
|
||||
// the getTypes() method which returns a copy of the internal PluginDescription array and can be accessed
|
||||
// in a thread-safe way.
|
||||
[[deprecated]] PluginDescription* getType (int index) noexcept { return &types.getReference (index); }
|
||||
[[deprecated]] const PluginDescription* getType (int index) const noexcept { return &types.getReference (index); }
|
||||
[[deprecated]] PluginDescription** begin() noexcept { jassertfalse; return nullptr; }
|
||||
[[deprecated]] PluginDescription* const* begin() const noexcept { jassertfalse; return nullptr; }
|
||||
[[deprecated]] PluginDescription** end() noexcept { jassertfalse; return nullptr; }
|
||||
[[deprecated]] PluginDescription* const* end() const noexcept { jassertfalse; return nullptr; }
|
||||
|
||||
// These methods have been deprecated in favour of their static counterparts. You should call getTypes()
|
||||
// to store the plug-in list at a point in time and use it when calling these methods.
|
||||
[[deprecated]] void addToMenu (PopupMenu& menu, SortMethod sortMethod, const String& currentlyTickedPluginID = {}) const;
|
||||
[[deprecated]] int getIndexChosenByMenu (int menuResultCode) const;
|
||||
[[deprecated]] std::unique_ptr<PluginTree> createTree (const SortMethod sortMethod) const;
|
||||
#endif
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
Array<PluginDescription> types;
|
||||
StringArray blacklist;
|
||||
std::unique_ptr<CustomScanner> scanner;
|
||||
CriticalSection scanLock, typesArrayLock;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (KnownPluginList)
|
||||
};
|
||||
|
||||
} // namespace juce
|
139
deps/juce/modules/juce_audio_processors/scanning/juce_PluginDirectoryScanner.cpp
vendored
Normal file
139
deps/juce/modules/juce_audio_processors/scanning/juce_PluginDirectoryScanner.cpp
vendored
Normal file
@ -0,0 +1,139 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
static StringArray readDeadMansPedalFile (const File& file)
|
||||
{
|
||||
StringArray lines;
|
||||
file.readLines (lines);
|
||||
lines.removeEmptyStrings();
|
||||
return lines;
|
||||
}
|
||||
|
||||
PluginDirectoryScanner::PluginDirectoryScanner (KnownPluginList& listToAddTo,
|
||||
AudioPluginFormat& formatToLookFor,
|
||||
FileSearchPath directoriesToSearch,
|
||||
const bool recursive,
|
||||
const File& deadMansPedal,
|
||||
bool allowPluginsWhichRequireAsynchronousInstantiation)
|
||||
: list (listToAddTo),
|
||||
format (formatToLookFor),
|
||||
deadMansPedalFile (deadMansPedal),
|
||||
allowAsync (allowPluginsWhichRequireAsynchronousInstantiation)
|
||||
{
|
||||
directoriesToSearch.removeRedundantPaths();
|
||||
setFilesOrIdentifiersToScan (format.searchPathsForPlugins (directoriesToSearch, recursive, allowAsync));
|
||||
}
|
||||
|
||||
PluginDirectoryScanner::~PluginDirectoryScanner()
|
||||
{
|
||||
list.scanFinished();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void PluginDirectoryScanner::setFilesOrIdentifiersToScan (const StringArray& filesOrIdentifiers)
|
||||
{
|
||||
filesOrIdentifiersToScan = filesOrIdentifiers;
|
||||
|
||||
// If any plugins have crashed recently when being loaded, move them to the
|
||||
// end of the list to give the others a chance to load correctly..
|
||||
for (auto& crashed : readDeadMansPedalFile (deadMansPedalFile))
|
||||
for (int j = filesOrIdentifiersToScan.size(); --j >= 0;)
|
||||
if (crashed == filesOrIdentifiersToScan[j])
|
||||
filesOrIdentifiersToScan.move (j, -1);
|
||||
|
||||
applyBlacklistingsFromDeadMansPedal (list, deadMansPedalFile);
|
||||
nextIndex.set (filesOrIdentifiersToScan.size());
|
||||
}
|
||||
|
||||
String PluginDirectoryScanner::getNextPluginFileThatWillBeScanned() const
|
||||
{
|
||||
return format.getNameOfPluginFromIdentifier (filesOrIdentifiersToScan [nextIndex.get() - 1]);
|
||||
}
|
||||
|
||||
void PluginDirectoryScanner::updateProgress()
|
||||
{
|
||||
progress = (1.0f - (float) nextIndex.get() / (float) filesOrIdentifiersToScan.size());
|
||||
}
|
||||
|
||||
bool PluginDirectoryScanner::scanNextFile (bool dontRescanIfAlreadyInList,
|
||||
String& nameOfPluginBeingScanned)
|
||||
{
|
||||
const int index = --nextIndex;
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
auto file = filesOrIdentifiersToScan [index];
|
||||
|
||||
if (file.isNotEmpty() && ! (dontRescanIfAlreadyInList && list.isListingUpToDate (file, format)))
|
||||
{
|
||||
nameOfPluginBeingScanned = format.getNameOfPluginFromIdentifier (file);
|
||||
|
||||
OwnedArray<PluginDescription> typesFound;
|
||||
|
||||
// Add this plugin to the end of the dead-man's pedal list in case it crashes...
|
||||
auto crashedPlugins = readDeadMansPedalFile (deadMansPedalFile);
|
||||
crashedPlugins.removeString (file);
|
||||
crashedPlugins.add (file);
|
||||
setDeadMansPedalFile (crashedPlugins);
|
||||
|
||||
list.scanAndAddFile (file, dontRescanIfAlreadyInList, typesFound, format);
|
||||
|
||||
// Managed to load without crashing, so remove it from the dead-man's-pedal..
|
||||
crashedPlugins.removeString (file);
|
||||
setDeadMansPedalFile (crashedPlugins);
|
||||
|
||||
if (typesFound.size() == 0 && ! list.getBlacklistedFiles().contains (file))
|
||||
failedFiles.add (file);
|
||||
}
|
||||
}
|
||||
|
||||
updateProgress();
|
||||
return index > 0;
|
||||
}
|
||||
|
||||
bool PluginDirectoryScanner::skipNextFile()
|
||||
{
|
||||
updateProgress();
|
||||
return --nextIndex > 0;
|
||||
}
|
||||
|
||||
void PluginDirectoryScanner::setDeadMansPedalFile (const StringArray& newContents)
|
||||
{
|
||||
if (deadMansPedalFile.getFullPathName().isNotEmpty())
|
||||
deadMansPedalFile.replaceWithText (newContents.joinIntoString ("\n"), true, true);
|
||||
}
|
||||
|
||||
void PluginDirectoryScanner::applyBlacklistingsFromDeadMansPedal (KnownPluginList& list, const File& file)
|
||||
{
|
||||
// If any plugins have crashed recently when being loaded, move them to the
|
||||
// end of the list to give the others a chance to load correctly..
|
||||
for (auto& crashedPlugin : readDeadMansPedalFile (file))
|
||||
list.addToBlacklist (crashedPlugin);
|
||||
}
|
||||
|
||||
} // namespace juce
|
138
deps/juce/modules/juce_audio_processors/scanning/juce_PluginDirectoryScanner.h
vendored
Normal file
138
deps/juce/modules/juce_audio_processors/scanning/juce_PluginDirectoryScanner.h
vendored
Normal file
@ -0,0 +1,138 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Scans a directory for plugins, and adds them to a KnownPluginList.
|
||||
|
||||
To use one of these, create it and call scanNextFile() repeatedly, until
|
||||
it returns false.
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class JUCE_API PluginDirectoryScanner
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/**
|
||||
Creates a scanner.
|
||||
|
||||
@param listToAddResultsTo this will get the new types added to it.
|
||||
@param formatToLookFor this is the type of format that you want to look for
|
||||
@param directoriesToSearch the path to search
|
||||
@param searchRecursively true to search recursively
|
||||
@param deadMansPedalFile if this isn't File(), then it will be used as a file
|
||||
to store the names of any plugins that crash during
|
||||
initialisation. If there are any plugins listed in it,
|
||||
then these will always be scanned after all other possible
|
||||
files have been tried - in this way, even if there's a few
|
||||
dodgy plugins in your path, then a couple of rescans
|
||||
will still manage to find all the proper plugins.
|
||||
It's probably best to choose a file in the user's
|
||||
application data directory (alongside your app's
|
||||
settings file) for this. The file format it uses
|
||||
is just a list of filenames of the modules that
|
||||
failed.
|
||||
@param allowPluginsWhichRequireAsynchronousInstantiation
|
||||
If this is false then the scanner will exclude plug-ins
|
||||
asynchronous creation - such as AUv3 plug-ins.
|
||||
*/
|
||||
PluginDirectoryScanner (KnownPluginList& listToAddResultsTo,
|
||||
AudioPluginFormat& formatToLookFor,
|
||||
FileSearchPath directoriesToSearch,
|
||||
bool searchRecursively,
|
||||
const File& deadMansPedalFile,
|
||||
bool allowPluginsWhichRequireAsynchronousInstantiation = false);
|
||||
|
||||
/** Destructor. */
|
||||
~PluginDirectoryScanner();
|
||||
|
||||
//==============================================================================
|
||||
/** Sets a specific list of filesOrIdentifiersToScan to scan.
|
||||
N.B. This list must match the format passed to the constructor.
|
||||
@see AudioPluginFormat::searchPathsForPlugins
|
||||
*/
|
||||
void setFilesOrIdentifiersToScan (const StringArray& filesOrIdentifiersToScan);
|
||||
|
||||
/** Tries the next likely-looking file.
|
||||
|
||||
If dontRescanIfAlreadyInList is true, then the file will only be loaded and
|
||||
re-tested if it's not already in the list, or if the file's modification
|
||||
time has changed since the list was created. If dontRescanIfAlreadyInList is
|
||||
false, the file will always be reloaded and tested.
|
||||
The nameOfPluginBeingScanned will be updated to the name of the plugin being
|
||||
scanned before the scan starts.
|
||||
|
||||
Returns false when there are no more files to try.
|
||||
*/
|
||||
bool scanNextFile (bool dontRescanIfAlreadyInList,
|
||||
String& nameOfPluginBeingScanned);
|
||||
|
||||
/** Skips over the next file without scanning it.
|
||||
Returns false when there are no more files to try.
|
||||
*/
|
||||
bool skipNextFile();
|
||||
|
||||
/** Returns the description of the plugin that will be scanned during the next
|
||||
call to scanNextFile().
|
||||
|
||||
This is handy if you want to show the user which file is currently getting
|
||||
scanned.
|
||||
*/
|
||||
String getNextPluginFileThatWillBeScanned() const;
|
||||
|
||||
/** Returns the estimated progress, between 0 and 1. */
|
||||
float getProgress() const { return progress; }
|
||||
|
||||
/** This returns a list of all the filenames of things that looked like being
|
||||
a plugin file, but which failed to open for some reason.
|
||||
*/
|
||||
const StringArray& getFailedFiles() const noexcept { return failedFiles; }
|
||||
|
||||
/** Reads the given dead-mans-pedal file and applies its contents to the list. */
|
||||
static void applyBlacklistingsFromDeadMansPedal (KnownPluginList& listToApplyTo,
|
||||
const File& deadMansPedalFile);
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
KnownPluginList& list;
|
||||
AudioPluginFormat& format;
|
||||
StringArray filesOrIdentifiersToScan;
|
||||
File deadMansPedalFile;
|
||||
StringArray failedFiles;
|
||||
Atomic<int> nextIndex;
|
||||
float progress = 0;
|
||||
const bool allowAsync;
|
||||
|
||||
void updateProgress();
|
||||
void setDeadMansPedalFile (const StringArray& newContents);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PluginDirectoryScanner)
|
||||
};
|
||||
|
||||
} // namespace juce
|
652
deps/juce/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp
vendored
Normal file
652
deps/juce/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp
vendored
Normal file
@ -0,0 +1,652 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class PluginListComponent::TableModel : public TableListBoxModel
|
||||
{
|
||||
public:
|
||||
TableModel (PluginListComponent& c, KnownPluginList& l) : owner (c), list (l) {}
|
||||
|
||||
int getNumRows() override
|
||||
{
|
||||
return list.getNumTypes() + list.getBlacklistedFiles().size();
|
||||
}
|
||||
|
||||
void paintRowBackground (Graphics& g, int /*rowNumber*/, int /*width*/, int /*height*/, bool rowIsSelected) override
|
||||
{
|
||||
const auto defaultColour = owner.findColour (ListBox::backgroundColourId);
|
||||
const auto c = rowIsSelected ? defaultColour.interpolatedWith (owner.findColour (ListBox::textColourId), 0.5f)
|
||||
: defaultColour;
|
||||
|
||||
g.fillAll (c);
|
||||
}
|
||||
|
||||
enum
|
||||
{
|
||||
nameCol = 1,
|
||||
typeCol = 2,
|
||||
categoryCol = 3,
|
||||
manufacturerCol = 4,
|
||||
descCol = 5
|
||||
};
|
||||
|
||||
void paintCell (Graphics& g, int row, int columnId, int width, int height, bool /*rowIsSelected*/) override
|
||||
{
|
||||
String text;
|
||||
bool isBlacklisted = row >= list.getNumTypes();
|
||||
|
||||
if (isBlacklisted)
|
||||
{
|
||||
if (columnId == nameCol)
|
||||
text = list.getBlacklistedFiles() [row - list.getNumTypes()];
|
||||
else if (columnId == descCol)
|
||||
text = TRANS("Deactivated after failing to initialise correctly");
|
||||
}
|
||||
else
|
||||
{
|
||||
auto desc = list.getTypes()[row];
|
||||
|
||||
switch (columnId)
|
||||
{
|
||||
case nameCol: text = desc.name; break;
|
||||
case typeCol: text = desc.pluginFormatName; break;
|
||||
case categoryCol: text = desc.category.isNotEmpty() ? desc.category : "-"; break;
|
||||
case manufacturerCol: text = desc.manufacturerName; break;
|
||||
case descCol: text = getPluginDescription (desc); break;
|
||||
|
||||
default: jassertfalse; break;
|
||||
}
|
||||
}
|
||||
|
||||
if (text.isNotEmpty())
|
||||
{
|
||||
const auto defaultTextColour = owner.findColour (ListBox::textColourId);
|
||||
g.setColour (isBlacklisted ? Colours::red
|
||||
: columnId == nameCol ? defaultTextColour
|
||||
: defaultTextColour.interpolatedWith (Colours::transparentBlack, 0.3f));
|
||||
g.setFont (Font ((float) height * 0.7f, Font::bold));
|
||||
g.drawFittedText (text, 4, 0, width - 6, height, Justification::centredLeft, 1, 0.9f);
|
||||
}
|
||||
}
|
||||
|
||||
void cellClicked (int rowNumber, int columnId, const juce::MouseEvent& e) override
|
||||
{
|
||||
TableListBoxModel::cellClicked (rowNumber, columnId, e);
|
||||
|
||||
if (rowNumber >= 0 && rowNumber < getNumRows() && e.mods.isPopupMenu())
|
||||
owner.createMenuForRow (rowNumber).showMenuAsync (PopupMenu::Options().withDeletionCheck (owner));
|
||||
}
|
||||
|
||||
void deleteKeyPressed (int) override
|
||||
{
|
||||
owner.removeSelectedPlugins();
|
||||
}
|
||||
|
||||
void sortOrderChanged (int newSortColumnId, bool isForwards) override
|
||||
{
|
||||
switch (newSortColumnId)
|
||||
{
|
||||
case nameCol: list.sort (KnownPluginList::sortAlphabetically, isForwards); break;
|
||||
case typeCol: list.sort (KnownPluginList::sortByFormat, isForwards); break;
|
||||
case categoryCol: list.sort (KnownPluginList::sortByCategory, isForwards); break;
|
||||
case manufacturerCol: list.sort (KnownPluginList::sortByManufacturer, isForwards); break;
|
||||
case descCol: break;
|
||||
|
||||
default: jassertfalse; break;
|
||||
}
|
||||
}
|
||||
|
||||
static String getPluginDescription (const PluginDescription& desc)
|
||||
{
|
||||
StringArray items;
|
||||
|
||||
if (desc.descriptiveName != desc.name)
|
||||
items.add (desc.descriptiveName);
|
||||
|
||||
items.add (desc.version);
|
||||
|
||||
items.removeEmptyStrings();
|
||||
return items.joinIntoString (" - ");
|
||||
}
|
||||
|
||||
PluginListComponent& owner;
|
||||
KnownPluginList& list;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TableModel)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
PluginListComponent::PluginListComponent (AudioPluginFormatManager& manager, KnownPluginList& listToEdit,
|
||||
const File& deadMansPedal, PropertiesFile* const props,
|
||||
bool allowPluginsWhichRequireAsynchronousInstantiation)
|
||||
: formatManager (manager),
|
||||
list (listToEdit),
|
||||
deadMansPedalFile (deadMansPedal),
|
||||
optionsButton ("Options..."),
|
||||
propertiesToUse (props),
|
||||
allowAsync (allowPluginsWhichRequireAsynchronousInstantiation),
|
||||
numThreads (allowAsync ? 1 : 0)
|
||||
{
|
||||
tableModel.reset (new TableModel (*this, listToEdit));
|
||||
|
||||
TableHeaderComponent& header = table.getHeader();
|
||||
|
||||
header.addColumn (TRANS("Name"), TableModel::nameCol, 200, 100, 700, TableHeaderComponent::defaultFlags | TableHeaderComponent::sortedForwards);
|
||||
header.addColumn (TRANS("Format"), TableModel::typeCol, 80, 80, 80, TableHeaderComponent::notResizable);
|
||||
header.addColumn (TRANS("Category"), TableModel::categoryCol, 100, 100, 200);
|
||||
header.addColumn (TRANS("Manufacturer"), TableModel::manufacturerCol, 200, 100, 300);
|
||||
header.addColumn (TRANS("Description"), TableModel::descCol, 300, 100, 500, TableHeaderComponent::notSortable);
|
||||
|
||||
table.setHeaderHeight (22);
|
||||
table.setRowHeight (20);
|
||||
table.setModel (tableModel.get());
|
||||
table.setMultipleSelectionEnabled (true);
|
||||
addAndMakeVisible (table);
|
||||
|
||||
addAndMakeVisible (optionsButton);
|
||||
optionsButton.onClick = [this]
|
||||
{
|
||||
createOptionsMenu().showMenuAsync (PopupMenu::Options()
|
||||
.withDeletionCheck (*this)
|
||||
.withTargetComponent (optionsButton));
|
||||
};
|
||||
|
||||
optionsButton.setTriggeredOnMouseDown (true);
|
||||
|
||||
setSize (400, 600);
|
||||
list.addChangeListener (this);
|
||||
updateList();
|
||||
table.getHeader().reSortTable();
|
||||
|
||||
PluginDirectoryScanner::applyBlacklistingsFromDeadMansPedal (list, deadMansPedalFile);
|
||||
deadMansPedalFile.deleteFile();
|
||||
}
|
||||
|
||||
PluginListComponent::~PluginListComponent()
|
||||
{
|
||||
list.removeChangeListener (this);
|
||||
}
|
||||
|
||||
void PluginListComponent::setOptionsButtonText (const String& newText)
|
||||
{
|
||||
optionsButton.setButtonText (newText);
|
||||
resized();
|
||||
}
|
||||
|
||||
void PluginListComponent::setScanDialogText (const String& title, const String& content)
|
||||
{
|
||||
dialogTitle = title;
|
||||
dialogText = content;
|
||||
}
|
||||
|
||||
void PluginListComponent::setNumberOfThreadsForScanning (int num)
|
||||
{
|
||||
numThreads = num;
|
||||
}
|
||||
|
||||
void PluginListComponent::resized()
|
||||
{
|
||||
auto r = getLocalBounds().reduced (2);
|
||||
|
||||
if (optionsButton.isVisible())
|
||||
{
|
||||
optionsButton.setBounds (r.removeFromBottom (24));
|
||||
optionsButton.changeWidthToFitText (24);
|
||||
r.removeFromBottom (3);
|
||||
}
|
||||
|
||||
table.setBounds (r);
|
||||
}
|
||||
|
||||
void PluginListComponent::changeListenerCallback (ChangeBroadcaster*)
|
||||
{
|
||||
table.getHeader().reSortTable();
|
||||
updateList();
|
||||
}
|
||||
|
||||
void PluginListComponent::updateList()
|
||||
{
|
||||
table.updateContent();
|
||||
table.repaint();
|
||||
}
|
||||
|
||||
void PluginListComponent::removeSelectedPlugins()
|
||||
{
|
||||
auto selected = table.getSelectedRows();
|
||||
|
||||
for (int i = table.getNumRows(); --i >= 0;)
|
||||
if (selected.contains (i))
|
||||
removePluginItem (i);
|
||||
}
|
||||
|
||||
void PluginListComponent::setTableModel (TableListBoxModel* model)
|
||||
{
|
||||
table.setModel (nullptr);
|
||||
tableModel.reset (model);
|
||||
table.setModel (tableModel.get());
|
||||
|
||||
table.getHeader().reSortTable();
|
||||
table.updateContent();
|
||||
table.repaint();
|
||||
}
|
||||
|
||||
static bool canShowFolderForPlugin (KnownPluginList& list, int index)
|
||||
{
|
||||
return File::createFileWithoutCheckingPath (list.getTypes()[index].fileOrIdentifier).exists();
|
||||
}
|
||||
|
||||
static void showFolderForPlugin (KnownPluginList& list, int index)
|
||||
{
|
||||
if (canShowFolderForPlugin (list, index))
|
||||
File (list.getTypes()[index].fileOrIdentifier).revealToUser();
|
||||
}
|
||||
|
||||
void PluginListComponent::removeMissingPlugins()
|
||||
{
|
||||
auto types = list.getTypes();
|
||||
|
||||
for (int i = types.size(); --i >= 0;)
|
||||
{
|
||||
auto type = types.getUnchecked (i);
|
||||
|
||||
if (! formatManager.doesPluginStillExist (type))
|
||||
list.removeType (type);
|
||||
}
|
||||
}
|
||||
|
||||
void PluginListComponent::removePluginItem (int index)
|
||||
{
|
||||
if (index < list.getNumTypes())
|
||||
list.removeType (list.getTypes()[index]);
|
||||
else
|
||||
list.removeFromBlacklist (list.getBlacklistedFiles() [index - list.getNumTypes()]);
|
||||
}
|
||||
|
||||
PopupMenu PluginListComponent::createOptionsMenu()
|
||||
{
|
||||
PopupMenu menu;
|
||||
menu.addItem (PopupMenu::Item (TRANS("Clear list"))
|
||||
.setAction ([this] { list.clear(); }));
|
||||
|
||||
menu.addSeparator();
|
||||
|
||||
for (auto format : formatManager.getFormats())
|
||||
if (format->canScanForPlugins())
|
||||
menu.addItem (PopupMenu::Item ("Remove all " + format->getName() + " plug-ins")
|
||||
.setEnabled (! list.getTypesForFormat (*format).isEmpty())
|
||||
.setAction ([this, format]
|
||||
{
|
||||
for (auto& pd : list.getTypesForFormat (*format))
|
||||
list.removeType (pd);
|
||||
}));
|
||||
|
||||
menu.addSeparator();
|
||||
|
||||
menu.addItem (PopupMenu::Item (TRANS("Remove selected plug-in from list"))
|
||||
.setEnabled (table.getNumSelectedRows() > 0)
|
||||
.setAction ([this] { removeSelectedPlugins(); }));
|
||||
|
||||
menu.addItem (PopupMenu::Item (TRANS("Remove any plug-ins whose files no longer exist"))
|
||||
.setAction ([this] { removeMissingPlugins(); }));
|
||||
|
||||
menu.addSeparator();
|
||||
|
||||
auto selectedRow = table.getSelectedRow();
|
||||
|
||||
menu.addItem (PopupMenu::Item (TRANS("Show folder containing selected plug-in"))
|
||||
.setEnabled (canShowFolderForPlugin (list, selectedRow))
|
||||
.setAction ([this, selectedRow] { showFolderForPlugin (list, selectedRow); }));
|
||||
|
||||
menu.addSeparator();
|
||||
|
||||
for (auto format : formatManager.getFormats())
|
||||
if (format->canScanForPlugins())
|
||||
menu.addItem (PopupMenu::Item ("Scan for new or updated " + format->getName() + " plug-ins")
|
||||
.setAction ([this, format] { scanFor (*format); }));
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
PopupMenu PluginListComponent::createMenuForRow (int rowNumber)
|
||||
{
|
||||
PopupMenu menu;
|
||||
|
||||
if (rowNumber >= 0 && rowNumber < tableModel->getNumRows())
|
||||
{
|
||||
menu.addItem (PopupMenu::Item (TRANS("Remove plug-in from list"))
|
||||
.setAction ([this, rowNumber] { removePluginItem (rowNumber); }));
|
||||
|
||||
menu.addItem (PopupMenu::Item (TRANS("Show folder containing plug-in"))
|
||||
.setEnabled (canShowFolderForPlugin (list, rowNumber))
|
||||
.setAction ([this, rowNumber] { showFolderForPlugin (list, rowNumber); }));
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
bool PluginListComponent::isInterestedInFileDrag (const StringArray& /*files*/)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void PluginListComponent::filesDropped (const StringArray& files, int, int)
|
||||
{
|
||||
OwnedArray<PluginDescription> typesFound;
|
||||
list.scanAndAddDragAndDroppedFiles (formatManager, files, typesFound);
|
||||
}
|
||||
|
||||
FileSearchPath PluginListComponent::getLastSearchPath (PropertiesFile& properties, AudioPluginFormat& format)
|
||||
{
|
||||
auto key = "lastPluginScanPath_" + format.getName();
|
||||
|
||||
if (properties.containsKey (key) && properties.getValue (key, {}).trim().isEmpty())
|
||||
properties.removeValue (key);
|
||||
|
||||
return FileSearchPath (properties.getValue (key, format.getDefaultLocationsToSearch().toString()));
|
||||
}
|
||||
|
||||
void PluginListComponent::setLastSearchPath (PropertiesFile& properties, AudioPluginFormat& format,
|
||||
const FileSearchPath& newPath)
|
||||
{
|
||||
auto key = "lastPluginScanPath_" + format.getName();
|
||||
|
||||
if (newPath.getNumPaths() == 0)
|
||||
properties.removeValue (key);
|
||||
else
|
||||
properties.setValue (key, newPath.toString());
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class PluginListComponent::Scanner : private Timer
|
||||
{
|
||||
public:
|
||||
Scanner (PluginListComponent& plc, AudioPluginFormat& format, const StringArray& filesOrIdentifiers,
|
||||
PropertiesFile* properties, bool allowPluginsWhichRequireAsynchronousInstantiation, int threads,
|
||||
const String& title, const String& text)
|
||||
: owner (plc), formatToScan (format), filesOrIdentifiersToScan (filesOrIdentifiers), propertiesToUse (properties),
|
||||
pathChooserWindow (TRANS("Select folders to scan..."), String(), MessageBoxIconType::NoIcon),
|
||||
progressWindow (title, text, MessageBoxIconType::NoIcon),
|
||||
numThreads (threads), allowAsync (allowPluginsWhichRequireAsynchronousInstantiation)
|
||||
{
|
||||
FileSearchPath path (formatToScan.getDefaultLocationsToSearch());
|
||||
|
||||
// You need to use at least one thread when scanning plug-ins asynchronously
|
||||
jassert (! allowAsync || (numThreads > 0));
|
||||
|
||||
// If the filesOrIdentifiersToScan argument isn't empty, we should only scan these
|
||||
// If the path is empty, then paths aren't used for this format.
|
||||
if (filesOrIdentifiersToScan.isEmpty() && path.getNumPaths() > 0)
|
||||
{
|
||||
#if ! JUCE_IOS
|
||||
if (propertiesToUse != nullptr)
|
||||
path = getLastSearchPath (*propertiesToUse, formatToScan);
|
||||
#endif
|
||||
|
||||
pathList.setSize (500, 300);
|
||||
pathList.setPath (path);
|
||||
|
||||
pathChooserWindow.addCustomComponent (&pathList);
|
||||
pathChooserWindow.addButton (TRANS("Scan"), 1, KeyPress (KeyPress::returnKey));
|
||||
pathChooserWindow.addButton (TRANS("Cancel"), 0, KeyPress (KeyPress::escapeKey));
|
||||
|
||||
pathChooserWindow.enterModalState (true,
|
||||
ModalCallbackFunction::forComponent (startScanCallback,
|
||||
&pathChooserWindow, this),
|
||||
false);
|
||||
}
|
||||
else
|
||||
{
|
||||
startScan();
|
||||
}
|
||||
}
|
||||
|
||||
~Scanner() override
|
||||
{
|
||||
if (pool != nullptr)
|
||||
{
|
||||
pool->removeAllJobs (true, 60000);
|
||||
pool.reset();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
PluginListComponent& owner;
|
||||
AudioPluginFormat& formatToScan;
|
||||
StringArray filesOrIdentifiersToScan;
|
||||
PropertiesFile* propertiesToUse;
|
||||
std::unique_ptr<PluginDirectoryScanner> scanner;
|
||||
AlertWindow pathChooserWindow, progressWindow;
|
||||
FileSearchPathListComponent pathList;
|
||||
String pluginBeingScanned;
|
||||
double progress = 0;
|
||||
int numThreads;
|
||||
bool allowAsync, finished = false, timerReentrancyCheck = false;
|
||||
std::unique_ptr<ThreadPool> pool;
|
||||
|
||||
static void startScanCallback (int result, AlertWindow* alert, Scanner* scanner)
|
||||
{
|
||||
if (alert != nullptr && scanner != nullptr)
|
||||
{
|
||||
if (result != 0)
|
||||
scanner->warnUserAboutStupidPaths();
|
||||
else
|
||||
scanner->finishedScan();
|
||||
}
|
||||
}
|
||||
|
||||
// Try to dissuade people from to scanning their entire C: drive, or other system folders.
|
||||
void warnUserAboutStupidPaths()
|
||||
{
|
||||
for (int i = 0; i < pathList.getPath().getNumPaths(); ++i)
|
||||
{
|
||||
auto f = pathList.getPath()[i];
|
||||
|
||||
if (isStupidPath (f))
|
||||
{
|
||||
AlertWindow::showOkCancelBox (MessageBoxIconType::WarningIcon,
|
||||
TRANS("Plugin Scanning"),
|
||||
TRANS("If you choose to scan folders that contain non-plugin files, "
|
||||
"then scanning may take a long time, and can cause crashes when "
|
||||
"attempting to load unsuitable files.")
|
||||
+ newLine
|
||||
+ TRANS ("Are you sure you want to scan the folder \"XYZ\"?")
|
||||
.replace ("XYZ", f.getFullPathName()),
|
||||
TRANS ("Scan"),
|
||||
String(),
|
||||
nullptr,
|
||||
ModalCallbackFunction::create (warnAboutStupidPathsCallback, this));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
startScan();
|
||||
}
|
||||
|
||||
static bool isStupidPath (const File& f)
|
||||
{
|
||||
Array<File> roots;
|
||||
File::findFileSystemRoots (roots);
|
||||
|
||||
if (roots.contains (f))
|
||||
return true;
|
||||
|
||||
File::SpecialLocationType pathsThatWouldBeStupidToScan[]
|
||||
= { File::globalApplicationsDirectory,
|
||||
File::userHomeDirectory,
|
||||
File::userDocumentsDirectory,
|
||||
File::userDesktopDirectory,
|
||||
File::tempDirectory,
|
||||
File::userMusicDirectory,
|
||||
File::userMoviesDirectory,
|
||||
File::userPicturesDirectory };
|
||||
|
||||
for (auto location : pathsThatWouldBeStupidToScan)
|
||||
{
|
||||
auto sillyFolder = File::getSpecialLocation (location);
|
||||
|
||||
if (f == sillyFolder || sillyFolder.isAChildOf (f))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void warnAboutStupidPathsCallback (int result, Scanner* scanner)
|
||||
{
|
||||
if (result != 0)
|
||||
scanner->startScan();
|
||||
else
|
||||
scanner->finishedScan();
|
||||
}
|
||||
|
||||
void startScan()
|
||||
{
|
||||
pathChooserWindow.setVisible (false);
|
||||
|
||||
scanner.reset (new PluginDirectoryScanner (owner.list, formatToScan, pathList.getPath(),
|
||||
true, owner.deadMansPedalFile, allowAsync));
|
||||
|
||||
if (! filesOrIdentifiersToScan.isEmpty())
|
||||
{
|
||||
scanner->setFilesOrIdentifiersToScan (filesOrIdentifiersToScan);
|
||||
}
|
||||
else if (propertiesToUse != nullptr)
|
||||
{
|
||||
setLastSearchPath (*propertiesToUse, formatToScan, pathList.getPath());
|
||||
propertiesToUse->saveIfNeeded();
|
||||
}
|
||||
|
||||
progressWindow.addButton (TRANS("Cancel"), 0, KeyPress (KeyPress::escapeKey));
|
||||
progressWindow.addProgressBarComponent (progress);
|
||||
progressWindow.enterModalState();
|
||||
|
||||
if (numThreads > 0)
|
||||
{
|
||||
pool.reset (new ThreadPool (numThreads));
|
||||
|
||||
for (int i = numThreads; --i >= 0;)
|
||||
pool->addJob (new ScanJob (*this), true);
|
||||
}
|
||||
|
||||
startTimer (20);
|
||||
}
|
||||
|
||||
void finishedScan()
|
||||
{
|
||||
owner.scanFinished (scanner != nullptr ? scanner->getFailedFiles()
|
||||
: StringArray());
|
||||
}
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
if (timerReentrancyCheck)
|
||||
return;
|
||||
|
||||
if (pool == nullptr)
|
||||
{
|
||||
const ScopedValueSetter<bool> setter (timerReentrancyCheck, true);
|
||||
|
||||
if (doNextScan())
|
||||
startTimer (20);
|
||||
}
|
||||
|
||||
if (! progressWindow.isCurrentlyModal())
|
||||
finished = true;
|
||||
|
||||
if (finished)
|
||||
finishedScan();
|
||||
else
|
||||
progressWindow.setMessage (TRANS("Testing") + ":\n\n" + pluginBeingScanned);
|
||||
}
|
||||
|
||||
bool doNextScan()
|
||||
{
|
||||
if (scanner->scanNextFile (true, pluginBeingScanned))
|
||||
{
|
||||
progress = scanner->getProgress();
|
||||
return true;
|
||||
}
|
||||
|
||||
finished = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
struct ScanJob : public ThreadPoolJob
|
||||
{
|
||||
ScanJob (Scanner& s) : ThreadPoolJob ("pluginscan"), scanner (s) {}
|
||||
|
||||
JobStatus runJob()
|
||||
{
|
||||
while (scanner.doNextScan() && ! shouldExit())
|
||||
{}
|
||||
|
||||
return jobHasFinished;
|
||||
}
|
||||
|
||||
Scanner& scanner;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ScanJob)
|
||||
};
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Scanner)
|
||||
};
|
||||
|
||||
void PluginListComponent::scanFor (AudioPluginFormat& format)
|
||||
{
|
||||
scanFor (format, StringArray());
|
||||
}
|
||||
|
||||
void PluginListComponent::scanFor (AudioPluginFormat& format, const StringArray& filesOrIdentifiersToScan)
|
||||
{
|
||||
currentScanner.reset (new Scanner (*this, format, filesOrIdentifiersToScan, propertiesToUse, allowAsync, numThreads,
|
||||
dialogTitle.isNotEmpty() ? dialogTitle : TRANS("Scanning for plug-ins..."),
|
||||
dialogText.isNotEmpty() ? dialogText : TRANS("Searching for all possible plug-in files...")));
|
||||
}
|
||||
|
||||
bool PluginListComponent::isScanning() const noexcept
|
||||
{
|
||||
return currentScanner != nullptr;
|
||||
}
|
||||
|
||||
void PluginListComponent::scanFinished (const StringArray& failedFiles)
|
||||
{
|
||||
StringArray shortNames;
|
||||
|
||||
for (auto& f : failedFiles)
|
||||
shortNames.add (File::createFileWithoutCheckingPath (f).getFileName());
|
||||
|
||||
currentScanner.reset(); // mustn't delete this before using the failed files array
|
||||
|
||||
if (shortNames.size() > 0)
|
||||
AlertWindow::showMessageBoxAsync (MessageBoxIconType::InfoIcon,
|
||||
TRANS("Scan complete"),
|
||||
TRANS("Note that the following files appeared to be plugin files, but failed to load correctly")
|
||||
+ ":\n\n"
|
||||
+ shortNames.joinIntoString (", "));
|
||||
}
|
||||
|
||||
} // namespace juce
|
143
deps/juce/modules/juce_audio_processors/scanning/juce_PluginListComponent.h
vendored
Normal file
143
deps/juce/modules/juce_audio_processors/scanning/juce_PluginListComponent.h
vendored
Normal file
@ -0,0 +1,143 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component displaying a list of plugins, with options to scan for them,
|
||||
add, remove and sort them.
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class JUCE_API PluginListComponent : public Component,
|
||||
public FileDragAndDropTarget,
|
||||
private ChangeListener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/**
|
||||
Creates the list component.
|
||||
|
||||
For info about the deadMansPedalFile, see the PluginDirectoryScanner constructor.
|
||||
The properties file, if supplied, is used to store the user's last search paths.
|
||||
*/
|
||||
PluginListComponent (AudioPluginFormatManager& formatManager,
|
||||
KnownPluginList& listToRepresent,
|
||||
const File& deadMansPedalFile,
|
||||
PropertiesFile* propertiesToUse,
|
||||
bool allowPluginsWhichRequireAsynchronousInstantiation = false);
|
||||
|
||||
/** Destructor. */
|
||||
~PluginListComponent() override;
|
||||
|
||||
/** Changes the text in the panel's options button. */
|
||||
void setOptionsButtonText (const String& newText);
|
||||
|
||||
/** Returns a pop-up menu that contains all the options for scanning and updating the list. */
|
||||
PopupMenu createOptionsMenu();
|
||||
|
||||
/** Returns a menu that can be shown if a row is right-clicked, containing actions
|
||||
like "remove plugin" or "show folder" etc.
|
||||
*/
|
||||
PopupMenu createMenuForRow (int rowNumber);
|
||||
|
||||
/** Changes the text in the progress dialog box that is shown when scanning. */
|
||||
void setScanDialogText (const String& textForProgressWindowTitle,
|
||||
const String& textForProgressWindowDescription);
|
||||
|
||||
/** Sets how many threads to simultaneously scan for plugins.
|
||||
If this is 0, then all scanning happens on the message thread (this is the default when
|
||||
allowPluginsWhichRequireAsynchronousInstantiation is false). If
|
||||
allowPluginsWhichRequireAsynchronousInstantiation is true then numThreads must not
|
||||
be zero (it is one by default). */
|
||||
void setNumberOfThreadsForScanning (int numThreads);
|
||||
|
||||
/** Returns the last search path stored in a given properties file for the specified format. */
|
||||
static FileSearchPath getLastSearchPath (PropertiesFile&, AudioPluginFormat&);
|
||||
|
||||
/** Stores a search path in a properties file for the given format. */
|
||||
static void setLastSearchPath (PropertiesFile&, AudioPluginFormat&, const FileSearchPath&);
|
||||
|
||||
/** Triggers an asynchronous scan for the given format. */
|
||||
void scanFor (AudioPluginFormat&);
|
||||
|
||||
/** Triggers an asynchronous scan for the given format and scans only the given files or identifiers.
|
||||
@see AudioPluginFormat::searchPathsForPlugins
|
||||
*/
|
||||
void scanFor (AudioPluginFormat&, const StringArray& filesOrIdentifiersToScan);
|
||||
|
||||
/** Returns true if there's currently a scan in progress. */
|
||||
bool isScanning() const noexcept;
|
||||
|
||||
/** Removes the plugins currently selected in the table. */
|
||||
void removeSelectedPlugins();
|
||||
|
||||
/** Sets a custom table model to be used.
|
||||
This will take ownership of the model and delete it when no longer needed.
|
||||
*/
|
||||
void setTableModel (TableListBoxModel*);
|
||||
|
||||
/** Returns the table used to display the plugin list. */
|
||||
TableListBox& getTableListBox() noexcept { return table; }
|
||||
|
||||
/** Returns the button used to display the options menu - you can make this invisible
|
||||
if you want to hide it and use some other method for showing the menu.
|
||||
*/
|
||||
TextButton& getOptionsButton() { return optionsButton; }
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
AudioPluginFormatManager& formatManager;
|
||||
KnownPluginList& list;
|
||||
File deadMansPedalFile;
|
||||
TableListBox table;
|
||||
TextButton optionsButton;
|
||||
PropertiesFile* propertiesToUse;
|
||||
String dialogTitle, dialogText;
|
||||
bool allowAsync;
|
||||
int numThreads;
|
||||
|
||||
class TableModel;
|
||||
std::unique_ptr<TableListBoxModel> tableModel;
|
||||
|
||||
class Scanner;
|
||||
std::unique_ptr<Scanner> currentScanner;
|
||||
|
||||
void scanFinished (const StringArray&);
|
||||
void updateList();
|
||||
void removeMissingPlugins();
|
||||
void removePluginItem (int index);
|
||||
|
||||
void resized() override;
|
||||
bool isInterestedInFileDrag (const StringArray&) override;
|
||||
void filesDropped (const StringArray&, int, int) override;
|
||||
void changeListenerCallback (ChangeBroadcaster*) override;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PluginListComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
Reference in New Issue
Block a user