- 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:
2015-02-21 17:09:43 +00:00
parent 843c6e300c
commit cfc9645b0b
1093 changed files with 426078 additions and 143 deletions
@@ -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
@@ -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