- port to minGw
git-svn-id: http://moon:8086/svn/software/trunk/projects/JaySynth@235 b431acfa-c32f-4a4a-93f1-934dc6c82436
This commit is contained in:
@@ -0,0 +1,549 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
KnownPluginList::KnownPluginList() {}
|
||||
KnownPluginList::~KnownPluginList() {}
|
||||
|
||||
void KnownPluginList::clear()
|
||||
{
|
||||
if (types.size() > 0)
|
||||
{
|
||||
types.clear();
|
||||
sendChangeMessage();
|
||||
}
|
||||
}
|
||||
|
||||
PluginDescription* KnownPluginList::getTypeForFile (const String& fileOrIdentifier) const
|
||||
{
|
||||
for (int i = 0; i < types.size(); ++i)
|
||||
if (types.getUnchecked(i)->fileOrIdentifier == fileOrIdentifier)
|
||||
return types.getUnchecked(i);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PluginDescription* KnownPluginList::getTypeForIdentifierString (const String& identifierString) const
|
||||
{
|
||||
for (int i = 0; i < types.size(); ++i)
|
||||
if (types.getUnchecked(i)->matchesIdentifierString (identifierString))
|
||||
return types.getUnchecked(i);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool KnownPluginList::addType (const PluginDescription& type)
|
||||
{
|
||||
for (int i = types.size(); --i >= 0;)
|
||||
{
|
||||
if (types.getUnchecked(i)->isDuplicateOf (type))
|
||||
{
|
||||
// strange - found a duplicate plugin with different info..
|
||||
jassert (types.getUnchecked(i)->name == type.name);
|
||||
jassert (types.getUnchecked(i)->isInstrument == type.isInstrument);
|
||||
|
||||
*types.getUnchecked(i) = type;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
types.insert (0, new PluginDescription (type));
|
||||
sendChangeMessage();
|
||||
return true;
|
||||
}
|
||||
|
||||
void KnownPluginList::removeType (const int index)
|
||||
{
|
||||
types.remove (index);
|
||||
sendChangeMessage();
|
||||
}
|
||||
|
||||
bool KnownPluginList::isListingUpToDate (const String& fileOrIdentifier,
|
||||
AudioPluginFormat& formatToUse) const
|
||||
{
|
||||
if (getTypeForFile (fileOrIdentifier) == nullptr)
|
||||
return false;
|
||||
|
||||
for (int i = types.size(); --i >= 0;)
|
||||
{
|
||||
const PluginDescription* const d = types.getUnchecked(i);
|
||||
|
||||
if (d->fileOrIdentifier == fileOrIdentifier
|
||||
&& formatToUse.pluginNeedsRescanning (*d))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void KnownPluginList::setCustomScanner (CustomScanner* newScanner)
|
||||
{
|
||||
scanner = 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;
|
||||
|
||||
for (int i = types.size(); --i >= 0;)
|
||||
{
|
||||
const PluginDescription* const d = types.getUnchecked(i);
|
||||
|
||||
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 (int i = 0; i < found.size(); ++i)
|
||||
{
|
||||
PluginDescription* const desc = found.getUnchecked(i);
|
||||
jassert (desc != nullptr);
|
||||
|
||||
addType (*desc);
|
||||
typesFound.add (new PluginDescription (*desc));
|
||||
}
|
||||
|
||||
return found.size() > 0;
|
||||
}
|
||||
|
||||
void KnownPluginList::scanAndAddDragAndDroppedFiles (AudioPluginFormatManager& formatManager,
|
||||
const StringArray& files,
|
||||
OwnedArray <PluginDescription>& typesFound)
|
||||
{
|
||||
for (int i = 0; i < files.size(); ++i)
|
||||
{
|
||||
const String filenameOrID (files[i]);
|
||||
bool found = false;
|
||||
|
||||
for (int j = 0; j < formatManager.getNumFormats(); ++j)
|
||||
{
|
||||
AudioPluginFormat* const format = formatManager.getFormat (j);
|
||||
|
||||
if (format->fileMightContainThisPluginType (filenameOrID)
|
||||
&& scanAndAddFile (filenameOrID, true, typesFound, *format))
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! found)
|
||||
{
|
||||
const File f (filenameOrID);
|
||||
|
||||
if (f.isDirectory())
|
||||
{
|
||||
StringArray s;
|
||||
|
||||
{
|
||||
Array<File> subFiles;
|
||||
f.findChildFiles (subFiles, File::findFilesAndDirectories, false);
|
||||
|
||||
for (int j = 0; j < subFiles.size(); ++j)
|
||||
s.add (subFiles.getReference(j).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) {}
|
||||
|
||||
int compareElements (const PluginDescription* const first,
|
||||
const PluginDescription* const second) const
|
||||
{
|
||||
int diff = 0;
|
||||
|
||||
switch (method)
|
||||
{
|
||||
case KnownPluginList::sortByCategory: diff = first->category.compareNatural (second->category); break;
|
||||
case KnownPluginList::sortByManufacturer: diff = first->manufacturerName.compareNatural (second->manufacturerName); break;
|
||||
case KnownPluginList::sortByFormat: diff = first->pluginFormatName.compare (second->pluginFormatName); break;
|
||||
case KnownPluginList::sortByFileSystemLocation: diff = lastPathPart (first->fileOrIdentifier).compare (lastPathPart (second->fileOrIdentifier)); break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (diff == 0)
|
||||
diff = first->name.compareNatural (second->name);
|
||||
|
||||
return diff * direction;
|
||||
}
|
||||
|
||||
private:
|
||||
static String lastPathPart (const String& path)
|
||||
{
|
||||
return path.replaceCharacter ('\\', '/').upToLastOccurrenceOf ("/", false, false);
|
||||
}
|
||||
|
||||
const KnownPluginList::SortMethod method;
|
||||
const int direction;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (PluginSorter)
|
||||
};
|
||||
|
||||
void KnownPluginList::sort (const SortMethod method, bool forwards)
|
||||
{
|
||||
if (method != defaultOrder)
|
||||
{
|
||||
Array<PluginDescription*> oldOrder, newOrder;
|
||||
oldOrder.addArray (types);
|
||||
|
||||
PluginSorter sorter (method, forwards);
|
||||
types.sort (sorter, true);
|
||||
|
||||
newOrder.addArray (types);
|
||||
|
||||
if (oldOrder != newOrder)
|
||||
sendChangeMessage();
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
XmlElement* KnownPluginList::createXml() const
|
||||
{
|
||||
XmlElement* const e = new XmlElement ("KNOWNPLUGINS");
|
||||
|
||||
for (int i = types.size(); --i >= 0;)
|
||||
e->prependChildElement (types.getUnchecked(i)->createXml());
|
||||
|
||||
for (int i = 0; i < blacklist.size(); ++i)
|
||||
e->createNewChildElement ("BLACKLISTED")->setAttribute ("id", blacklist[i]);
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
void KnownPluginList::recreateFromXml (const XmlElement& xml)
|
||||
{
|
||||
clear();
|
||||
clearBlacklistedFiles();
|
||||
|
||||
if (xml.hasTagName ("KNOWNPLUGINS"))
|
||||
{
|
||||
forEachXmlChildElement (xml, e)
|
||||
{
|
||||
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 (int i = 0; i < allPlugins.size(); ++i)
|
||||
{
|
||||
PluginDescription* const pd = allPlugins.getUnchecked (i);
|
||||
|
||||
String 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;)
|
||||
{
|
||||
KnownPluginList::PluginTree& sub = *tree.subFolders.getUnchecked(i);
|
||||
optimiseFolders (sub, concatenateName || (tree.subFolders.size() > 1));
|
||||
|
||||
if (sub.plugins.size() == 0)
|
||||
{
|
||||
for (int j = 0; j < sub.subFolders.size(); ++j)
|
||||
{
|
||||
KnownPluginList::PluginTree* const s = sub.subFolders.getUnchecked(j);
|
||||
|
||||
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;
|
||||
ScopedPointer<KnownPluginList::PluginTree> current (new KnownPluginList::PluginTree());
|
||||
|
||||
for (int i = 0; i < sorted.size(); ++i)
|
||||
{
|
||||
const PluginDescription* const pd = sorted.getUnchecked(i);
|
||||
String thisType (sortMethod == KnownPluginList::sortByCategory ? pd->category
|
||||
: pd->manufacturerName);
|
||||
|
||||
if (! thisType.containsNonWhitespaceChars())
|
||||
thisType = "Other";
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (current->plugins.size() + current->subFolders.size() > 0)
|
||||
{
|
||||
current->folder = lastType;
|
||||
tree.subFolders.add (current.release());
|
||||
}
|
||||
}
|
||||
|
||||
static void addPlugin (KnownPluginList::PluginTree& tree, PluginDescription* const pd, String path)
|
||||
{
|
||||
if (path.isEmpty())
|
||||
{
|
||||
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));
|
||||
|
||||
for (int i = tree.subFolders.size(); --i >= 0;)
|
||||
{
|
||||
KnownPluginList::PluginTree& subFolder = *tree.subFolders.getUnchecked(i);
|
||||
|
||||
if (subFolder.folder.equalsIgnoreCase (firstSubFolder))
|
||||
{
|
||||
addPlugin (subFolder, pd, remainingPath);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
KnownPluginList::PluginTree* const newFolder = new KnownPluginList::PluginTree();
|
||||
newFolder->folder = firstSubFolder;
|
||||
tree.subFolders.add (newFolder);
|
||||
addPlugin (*newFolder, pd, remainingPath);
|
||||
}
|
||||
}
|
||||
|
||||
static bool containsDuplicateNames (const Array<const PluginDescription*>& plugins, const String& name)
|
||||
{
|
||||
int matches = 0;
|
||||
|
||||
for (int i = 0; i < plugins.size(); ++i)
|
||||
if (plugins.getUnchecked(i)->name == name)
|
||||
if (++matches > 1)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
String name (plugin->name);
|
||||
|
||||
if (containsDuplicateNames (tree.plugins, name))
|
||||
name << " (" << plugin->pluginFormatName << ')';
|
||||
|
||||
m.addItem (allPlugins.indexOf (plugin) + menuIdBase, name, true, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
KnownPluginList::PluginTree* KnownPluginList::createTree (const SortMethod sortMethod) const
|
||||
{
|
||||
Array <PluginDescription*> sorted;
|
||||
|
||||
{
|
||||
PluginSorter sorter (sortMethod, true);
|
||||
|
||||
for (int i = 0; i < types.size(); ++i)
|
||||
sorted.addSorted (sorter, types.getUnchecked(i));
|
||||
}
|
||||
|
||||
PluginTree* tree = new PluginTree();
|
||||
|
||||
if (sortMethod == sortByCategory || sortMethod == sortByManufacturer || sortMethod == sortByFormat)
|
||||
{
|
||||
PluginTreeUtils::buildTreeByCategory (*tree, sorted, sortMethod);
|
||||
}
|
||||
else if (sortMethod == sortByFileSystemLocation)
|
||||
{
|
||||
PluginTreeUtils::buildTreeByFolder (*tree, sorted);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < sorted.size(); ++i)
|
||||
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 - 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 (ThreadPoolJob* job = ThreadPoolJob::getCurrentThreadPoolJob())
|
||||
return job->shouldExit();
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_KNOWNPLUGINLIST_H_INCLUDED
|
||||
#define JUCE_KNOWNPLUGINLIST_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
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
|
||||
*/
|
||||
class JUCE_API KnownPluginList : public ChangeBroadcaster
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an empty list. */
|
||||
KnownPluginList();
|
||||
|
||||
/** Destructor. */
|
||||
~KnownPluginList();
|
||||
|
||||
//==============================================================================
|
||||
/** Clears the list. */
|
||||
void clear();
|
||||
|
||||
/** Returns the number of types currently in the list.
|
||||
@see getType
|
||||
*/
|
||||
int getNumTypes() const noexcept { return types.size(); }
|
||||
|
||||
/** Returns one of the types.
|
||||
@see getNumTypes
|
||||
*/
|
||||
PluginDescription* getType (int index) const noexcept { return types [index]; }
|
||||
|
||||
/** Type iteration. */
|
||||
PluginDescription** begin() const noexcept { return types.begin(); }
|
||||
/** Type iteration. */
|
||||
PluginDescription** end() const noexcept { return types.end(); }
|
||||
|
||||
/** 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.
|
||||
|
||||
The identifierString parameter must have been created by
|
||||
PluginDescription::createIdentifierString().
|
||||
*/
|
||||
PluginDescription* getTypeForIdentifierString (const String& identifierString) const;
|
||||
|
||||
/** Adds a type manually from its description. */
|
||||
bool addType (const PluginDescription& type);
|
||||
|
||||
/** Removes a type. */
|
||||
void removeType (int index);
|
||||
|
||||
/** 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
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Adds all the plugin 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.
|
||||
*/
|
||||
void addToMenu (PopupMenu& menu, 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;
|
||||
|
||||
//==============================================================================
|
||||
/** Sorts the list. */
|
||||
void sort (SortMethod method, bool forwards);
|
||||
|
||||
//==============================================================================
|
||||
/** Creates some XML that can be used to store the state of this list. */
|
||||
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<const PluginDescription*> plugins;
|
||||
};
|
||||
|
||||
/** Creates a PluginTree object containing all the known plugins. */
|
||||
PluginTree* createTree (const SortMethod sortMethod) const;
|
||||
|
||||
//==============================================================================
|
||||
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 (CustomScanner*);
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
OwnedArray<PluginDescription> types;
|
||||
StringArray blacklist;
|
||||
ScopedPointer<CustomScanner> scanner;
|
||||
CriticalSection scanLock;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (KnownPluginList)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_KNOWNPLUGINLIST_H_INCLUDED
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
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)
|
||||
: list (listToAddTo),
|
||||
format (formatToLookFor),
|
||||
deadMansPedalFile (deadMansPedal),
|
||||
progress (0)
|
||||
{
|
||||
directoriesToSearch.removeRedundantPaths();
|
||||
|
||||
filesOrIdentifiersToScan = format.searchPathsForPlugins (directoriesToSearch, recursive);
|
||||
|
||||
// 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..
|
||||
const StringArray crashedPlugins (readDeadMansPedalFile (deadMansPedalFile));
|
||||
|
||||
for (int i = 0; i < crashedPlugins.size(); ++i)
|
||||
{
|
||||
const String f = crashedPlugins[i];
|
||||
|
||||
for (int j = filesOrIdentifiersToScan.size(); --j >= 0;)
|
||||
if (f == filesOrIdentifiersToScan[j])
|
||||
filesOrIdentifiersToScan.move (j, -1);
|
||||
}
|
||||
|
||||
applyBlacklistingsFromDeadMansPedal (listToAddTo, deadMansPedalFile);
|
||||
nextIndex.set (filesOrIdentifiersToScan.size());
|
||||
}
|
||||
|
||||
PluginDirectoryScanner::~PluginDirectoryScanner()
|
||||
{
|
||||
list.scanFinished();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
String PluginDirectoryScanner::getNextPluginFileThatWillBeScanned() const
|
||||
{
|
||||
return format.getNameOfPluginFromIdentifier (filesOrIdentifiersToScan [nextIndex.get() - 1]);
|
||||
}
|
||||
|
||||
void PluginDirectoryScanner::updateProgress()
|
||||
{
|
||||
progress = (1.0f - nextIndex.get() / (float) filesOrIdentifiersToScan.size());
|
||||
}
|
||||
|
||||
bool PluginDirectoryScanner::scanNextFile (const bool dontRescanIfAlreadyInList,
|
||||
String& nameOfPluginBeingScanned)
|
||||
{
|
||||
const int index = --nextIndex;
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
const String file (filesOrIdentifiersToScan [index]);
|
||||
|
||||
if (file.isNotEmpty() && ! 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...
|
||||
StringArray 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..
|
||||
const StringArray crashedPlugins (readDeadMansPedalFile (file));
|
||||
|
||||
for (int i = 0; i < crashedPlugins.size(); ++i)
|
||||
list.addToBlacklist (crashedPlugins[i]);
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_PLUGINDIRECTORYSCANNER_H_INCLUDED
|
||||
#define JUCE_PLUGINDIRECTORYSCANNER_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
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.
|
||||
*/
|
||||
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::nonexistent, 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.
|
||||
*/
|
||||
PluginDirectoryScanner (KnownPluginList& listToAddResultsTo,
|
||||
AudioPluginFormat& formatToLookFor,
|
||||
FileSearchPath directoriesToSearch,
|
||||
bool searchRecursively,
|
||||
const File& deadMansPedalFile);
|
||||
|
||||
/** Destructor. */
|
||||
~PluginDirectoryScanner();
|
||||
|
||||
//==============================================================================
|
||||
/** 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;
|
||||
|
||||
void updateProgress();
|
||||
void setDeadMansPedalFile (const StringArray& newContents);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PluginDirectoryScanner)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_PLUGINDIRECTORYSCANNER_H_INCLUDED
|
||||
@@ -0,0 +1,554 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
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
|
||||
{
|
||||
if (rowIsSelected)
|
||||
g.fillAll (owner.findColour (TextEditor::highlightColourId));
|
||||
}
|
||||
|
||||
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 if (const PluginDescription* const desc = list.getType (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())
|
||||
{
|
||||
g.setColour (isBlacklisted ? Colours::red
|
||||
: columnId == nameCol ? Colours::black
|
||||
: Colours::grey);
|
||||
g.setFont (Font (height * 0.7f, Font::bold));
|
||||
g.drawFittedText (text, 4, 0, width - 6, height, Justification::centredLeft, 1, 0.9f);
|
||||
}
|
||||
}
|
||||
|
||||
void deleteKeyPressed (int) override
|
||||
{
|
||||
owner.removeSelected();
|
||||
}
|
||||
|
||||
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 void removePluginItem (KnownPluginList& list, int index)
|
||||
{
|
||||
if (index < list.getNumTypes())
|
||||
list.removeType (index);
|
||||
else
|
||||
list.removeFromBlacklist (list.getBlacklistedFiles() [index - list.getNumTypes()]);
|
||||
}
|
||||
|
||||
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)
|
||||
: formatManager (manager),
|
||||
list (listToEdit),
|
||||
deadMansPedalFile (deadMansPedal),
|
||||
optionsButton ("Options..."),
|
||||
propertiesToUse (props),
|
||||
numThreads (0)
|
||||
{
|
||||
tableModel = 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);
|
||||
table.setMultipleSelectionEnabled (true);
|
||||
addAndMakeVisible (table);
|
||||
|
||||
addAndMakeVisible (optionsButton);
|
||||
optionsButton.addListener (this);
|
||||
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::setNumberOfThreadsForScanning (int num)
|
||||
{
|
||||
numThreads = num;
|
||||
}
|
||||
|
||||
void PluginListComponent::resized()
|
||||
{
|
||||
Rectangle<int> r (getLocalBounds().reduced (2));
|
||||
|
||||
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::removeSelected()
|
||||
{
|
||||
const SparseSet<int> selected (table.getSelectedRows());
|
||||
|
||||
for (int i = table.getNumRows(); --i >= 0;)
|
||||
if (selected.contains (i))
|
||||
TableModel::removePluginItem (list, i);
|
||||
}
|
||||
|
||||
bool PluginListComponent::canShowSelectedFolder() const
|
||||
{
|
||||
if (const PluginDescription* const desc = list.getType (table.getSelectedRow()))
|
||||
return File::createFileWithoutCheckingPath (desc->fileOrIdentifier).exists();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void PluginListComponent::showSelectedFolder()
|
||||
{
|
||||
if (canShowSelectedFolder())
|
||||
if (const PluginDescription* const desc = list.getType (table.getSelectedRow()))
|
||||
File (desc->fileOrIdentifier).getParentDirectory().startAsProcess();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (pluginList != nullptr)
|
||||
pluginList->optionsMenuCallback (result);
|
||||
}
|
||||
|
||||
void PluginListComponent::optionsMenuCallback (int result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case 0: break;
|
||||
case 1: list.clear(); break;
|
||||
case 2: removeSelected(); break;
|
||||
case 3: showSelectedFolder(); break;
|
||||
case 4: removeMissingPlugins(); break;
|
||||
|
||||
default:
|
||||
if (AudioPluginFormat* format = formatManager.getFormat (result - 10))
|
||||
scanFor (*format);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void PluginListComponent::buttonClicked (Button* button)
|
||||
{
|
||||
if (button == &optionsButton)
|
||||
{
|
||||
PopupMenu menu;
|
||||
menu.addItem (1, TRANS("Clear list"));
|
||||
menu.addItem (2, TRANS("Remove selected plug-in from list"), table.getNumSelectedRows() > 0);
|
||||
menu.addItem (3, TRANS("Show folder containing selected plug-in"), canShowSelectedFolder());
|
||||
menu.addItem (4, TRANS("Remove any plug-ins whose files no longer exist"));
|
||||
menu.addSeparator();
|
||||
|
||||
for (int i = 0; i < formatManager.getNumFormats(); ++i)
|
||||
{
|
||||
AudioPluginFormat* const format = formatManager.getFormat (i);
|
||||
|
||||
if (format->canScanForPlugins())
|
||||
menu.addItem (10 + i, "Scan for new or updated " + format->getName() + " plug-ins");
|
||||
}
|
||||
|
||||
menu.showMenuAsync (PopupMenu::Options().withTargetComponent (&optionsButton),
|
||||
ModalCallbackFunction::forComponent (optionsMenuStaticCallback, this));
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
return FileSearchPath (properties.getValue ("lastPluginScanPath_" + format.getName(),
|
||||
format.getDefaultLocationsToSearch().toString()));
|
||||
}
|
||||
|
||||
void PluginListComponent::setLastSearchPath (PropertiesFile& properties, AudioPluginFormat& format,
|
||||
const FileSearchPath& newPath)
|
||||
{
|
||||
properties.setValue ("lastPluginScanPath_" + format.getName(), newPath.toString());
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class PluginListComponent::Scanner : private Timer
|
||||
{
|
||||
public:
|
||||
Scanner (PluginListComponent& plc, AudioPluginFormat& format, PropertiesFile* properties, int threads)
|
||||
: owner (plc), formatToScan (format), propertiesToUse (properties),
|
||||
pathChooserWindow (TRANS("Select folders to scan..."), String::empty, AlertWindow::NoIcon),
|
||||
progressWindow (TRANS("Scanning for plug-ins..."),
|
||||
TRANS("Searching for all possible plug-in files..."), AlertWindow::NoIcon),
|
||||
progress (0.0), numThreads (threads), finished (false)
|
||||
{
|
||||
FileSearchPath path (formatToScan.getDefaultLocationsToSearch());
|
||||
|
||||
if (path.getNumPaths() > 0) // if the path is empty, then paths aren't used for this format.
|
||||
{
|
||||
if (propertiesToUse != nullptr)
|
||||
path = getLastSearchPath (*propertiesToUse, formatToScan);
|
||||
|
||||
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()
|
||||
{
|
||||
if (pool != nullptr)
|
||||
{
|
||||
pool->removeAllJobs (true, 60000);
|
||||
pool = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
PluginListComponent& owner;
|
||||
AudioPluginFormat& formatToScan;
|
||||
PropertiesFile* propertiesToUse;
|
||||
ScopedPointer<PluginDirectoryScanner> scanner;
|
||||
AlertWindow pathChooserWindow, progressWindow;
|
||||
FileSearchPathListComponent pathList;
|
||||
String pluginBeingScanned;
|
||||
double progress;
|
||||
int numThreads;
|
||||
bool finished;
|
||||
ScopedPointer<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)
|
||||
{
|
||||
const File f (pathList.getPath()[i]);
|
||||
|
||||
if (isStupidPath (f))
|
||||
{
|
||||
AlertWindow::showOkCancelBox (AlertWindow::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::empty,
|
||||
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 (int i = 0; i < numElementsInArray (pathsThatWouldBeStupidToScan); ++i)
|
||||
{
|
||||
const File sillyFolder (File::getSpecialLocation (pathsThatWouldBeStupidToScan[i]));
|
||||
|
||||
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 = new PluginDirectoryScanner (owner.list, formatToScan, pathList.getPath(),
|
||||
true, owner.deadMansPedalFile);
|
||||
|
||||
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 = 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 (pool == nullptr)
|
||||
{
|
||||
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)
|
||||
{
|
||||
currentScanner = new Scanner (*this, format, propertiesToUse, numThreads);
|
||||
}
|
||||
|
||||
bool PluginListComponent::isScanning() const noexcept
|
||||
{
|
||||
return currentScanner != nullptr;
|
||||
}
|
||||
|
||||
void PluginListComponent::scanFinished (const StringArray& failedFiles)
|
||||
{
|
||||
StringArray shortNames;
|
||||
|
||||
for (int i = 0; i < failedFiles.size(); ++i)
|
||||
shortNames.add (File::createFileWithoutCheckingPath (failedFiles[i]).getFileName());
|
||||
|
||||
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 (", "));
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_PLUGINLISTCOMPONENT_H_INCLUDED
|
||||
#define JUCE_PLUGINLISTCOMPONENT_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component displaying a list of plugins, with options to scan for them,
|
||||
add, remove and sort them.
|
||||
*/
|
||||
class JUCE_API PluginListComponent : public Component,
|
||||
public FileDragAndDropTarget,
|
||||
private ChangeListener,
|
||||
private ButtonListener // (can't use Button::Listener due to idiotic VC2005 bug)
|
||||
{
|
||||
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);
|
||||
|
||||
/** Destructor. */
|
||||
~PluginListComponent();
|
||||
|
||||
/** Changes the text in the panel's options button. */
|
||||
void setOptionsButtonText (const String& newText);
|
||||
|
||||
/** 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)
|
||||
*/
|
||||
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&);
|
||||
|
||||
/** Returns true if there's currently a scan in progress. */
|
||||
bool isScanning() const noexcept;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
AudioPluginFormatManager& formatManager;
|
||||
KnownPluginList& list;
|
||||
File deadMansPedalFile;
|
||||
TableListBox table;
|
||||
TextButton optionsButton;
|
||||
PropertiesFile* propertiesToUse;
|
||||
int numThreads;
|
||||
|
||||
class TableModel;
|
||||
friend class TableModel;
|
||||
friend struct ContainerDeletePolicy<TableModel>;
|
||||
ScopedPointer<TableModel> tableModel;
|
||||
|
||||
class Scanner;
|
||||
friend class Scanner;
|
||||
friend struct ContainerDeletePolicy<Scanner>;
|
||||
ScopedPointer<Scanner> currentScanner;
|
||||
|
||||
void scanFinished (const StringArray&);
|
||||
static void optionsMenuStaticCallback (int, PluginListComponent*);
|
||||
void optionsMenuCallback (int);
|
||||
void updateList();
|
||||
void showSelectedFolder();
|
||||
bool canShowSelectedFolder() const;
|
||||
void removeSelected();
|
||||
void removeMissingPlugins();
|
||||
|
||||
void resized() override;
|
||||
bool isInterestedInFileDrag (const StringArray&) override;
|
||||
void filesDropped (const StringArray&, int, int) override;
|
||||
void buttonClicked (Button*) override;
|
||||
void changeListenerCallback (ChangeBroadcaster*) override;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PluginListComponent)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_PLUGINLISTCOMPONENT_H_INCLUDED
|
||||
Reference in New Issue
Block a user