2 ==============================================================================
4 This file is part of the JUCE library - "Jules' Utility Class Extensions"
5 Copyright 2004-11 by Raw Material Software Ltd.
7 ------------------------------------------------------------------------------
9 JUCE can be redistributed and/or modified under the terms of the GNU General
10 Public License (Version 2), as published by the Free Software Foundation.
11 A copy of the license is included in the JUCE distribution, or can be found
12 online at www.gnu.org/licenses.
14 JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 A PARTICULAR PURPOSE. See the GNU General Public License for more details.
18 ------------------------------------------------------------------------------
20 To release a closed-source product which uses JUCE, commercial licenses are
21 available: visit www.rawmaterialsoftware.com/juce for more information.
23 ==============================================================================
26 #include "../../core/juce_StandardHeader.h"
30 #include "juce_KnownPluginList.h"
31 #include "juce_AudioPluginFormatManager.h"
34 //==============================================================================
35 KnownPluginList::KnownPluginList()
39 KnownPluginList::~KnownPluginList()
43 void KnownPluginList::clear()
52 PluginDescription
* KnownPluginList::getTypeForFile (const String
& fileOrIdentifier
) const
54 for (int i
= 0; i
< types
.size(); ++i
)
55 if (types
.getUnchecked(i
)->fileOrIdentifier
== fileOrIdentifier
)
56 return types
.getUnchecked(i
);
61 PluginDescription
* KnownPluginList::getTypeForIdentifierString (const String
& identifierString
) const
63 for (int i
= 0; i
< types
.size(); ++i
)
64 if (types
.getUnchecked(i
)->createIdentifierString() == identifierString
)
65 return types
.getUnchecked(i
);
70 bool KnownPluginList::addType (const PluginDescription
& type
)
72 for (int i
= types
.size(); --i
>= 0;)
74 if (types
.getUnchecked(i
)->isDuplicateOf (type
))
76 // strange - found a duplicate plugin with different info..
77 jassert (types
.getUnchecked(i
)->name
== type
.name
);
78 jassert (types
.getUnchecked(i
)->isInstrument
== type
.isInstrument
);
80 *types
.getUnchecked(i
) = type
;
85 types
.add (new PluginDescription (type
));
90 void KnownPluginList::removeType (const int index
)
98 Time
getPluginFileModTime (const String
& fileOrIdentifier
)
100 if (fileOrIdentifier
.startsWithChar ('/') || fileOrIdentifier
[1] == ':')
101 return File (fileOrIdentifier
).getLastModificationTime();
106 bool timesAreDifferent (const Time
& t1
, const Time
& t2
) noexcept
108 return t1
!= t2
|| t1
== Time();
112 bool KnownPluginList::isListingUpToDate (const String
& fileOrIdentifier
) const
114 if (getTypeForFile (fileOrIdentifier
) == 0)
117 for (int i
= types
.size(); --i
>= 0;)
119 const PluginDescription
* const d
= types
.getUnchecked(i
);
121 if (d
->fileOrIdentifier
== fileOrIdentifier
122 && timesAreDifferent (d
->lastFileModTime
, getPluginFileModTime (fileOrIdentifier
)))
131 bool KnownPluginList::scanAndAddFile (const String
& fileOrIdentifier
,
132 const bool dontRescanIfAlreadyInList
,
133 OwnedArray
<PluginDescription
>& typesFound
,
134 AudioPluginFormat
& format
)
136 bool addedOne
= false;
138 if (dontRescanIfAlreadyInList
139 && getTypeForFile (fileOrIdentifier
) != nullptr)
141 bool needsRescanning
= false;
143 for (int i
= types
.size(); --i
>= 0;)
145 const PluginDescription
* const d
= types
.getUnchecked(i
);
147 if (d
->fileOrIdentifier
== fileOrIdentifier
)
149 if (timesAreDifferent (d
->lastFileModTime
, getPluginFileModTime (fileOrIdentifier
)))
150 needsRescanning
= true;
152 typesFound
.add (new PluginDescription (*d
));
156 if (! needsRescanning
)
160 OwnedArray
<PluginDescription
> found
;
161 format
.findAllTypesForFile (found
, fileOrIdentifier
);
163 for (int i
= 0; i
< found
.size(); ++i
)
165 PluginDescription
* const desc
= found
.getUnchecked(i
);
166 jassert (desc
!= nullptr);
171 typesFound
.add (new PluginDescription (*desc
));
177 void KnownPluginList::scanAndAddDragAndDroppedFiles (const StringArray
& files
,
178 OwnedArray
<PluginDescription
>& typesFound
)
180 for (int i
= 0; i
< files
.size(); ++i
)
182 for (int j
= 0; j
< AudioPluginFormatManager::getInstance()->getNumFormats(); ++j
)
184 AudioPluginFormat
* const format
= AudioPluginFormatManager::getInstance()->getFormat (j
);
186 if (scanAndAddFile (files
[i
], true, typesFound
, *format
))
190 const File
f (files
[i
]);
197 Array
<File
> subFiles
;
198 f
.findChildFiles (subFiles
, File::findFilesAndDirectories
, false);
200 for (int j
= 0; j
< subFiles
.size(); ++j
)
201 s
.add (subFiles
.getReference(j
).getFullPathName());
204 scanAndAddDragAndDroppedFiles (s
, typesFound
);
209 //==============================================================================
213 KnownPluginList::SortMethod method
;
215 PluginSorter() noexcept
{}
217 int compareElements (const PluginDescription
* const first
,
218 const PluginDescription
* const second
) const
222 if (method
== KnownPluginList::sortByCategory
)
223 diff
= first
->category
.compareLexicographically (second
->category
);
224 else if (method
== KnownPluginList::sortByManufacturer
)
225 diff
= first
->manufacturerName
.compareLexicographically (second
->manufacturerName
);
226 else if (method
== KnownPluginList::sortByFileSystemLocation
)
227 diff
= first
->fileOrIdentifier
.replaceCharacter ('\\', '/')
228 .upToLastOccurrenceOf ("/", false, false)
229 .compare (second
->fileOrIdentifier
.replaceCharacter ('\\', '/')
230 .upToLastOccurrenceOf ("/", false, false));
233 diff
= first
->name
.compareLexicographically (second
->name
);
239 void KnownPluginList::sort (const SortMethod method
)
241 if (method
!= defaultOrder
)
244 sorter
.method
= method
;
245 types
.sort (sorter
, true);
251 //==============================================================================
252 XmlElement
* KnownPluginList::createXml() const
254 XmlElement
* const e
= new XmlElement ("KNOWNPLUGINS");
256 for (int i
= 0; i
< types
.size(); ++i
)
257 e
->addChildElement (types
.getUnchecked(i
)->createXml());
262 void KnownPluginList::recreateFromXml (const XmlElement
& xml
)
266 if (xml
.hasTagName ("KNOWNPLUGINS"))
268 forEachXmlChildElement (xml
, e
)
270 PluginDescription info
;
272 if (info
.loadFromXml (*e
))
278 //==============================================================================
279 const int menuIdBase
= 0x324503f4;
281 // This is used to turn a bunch of paths into a nested menu structure.
282 struct PluginFilesystemTree
286 OwnedArray
<PluginFilesystemTree
> subFolders
;
287 Array
<PluginDescription
*> plugins
;
289 void addPlugin (PluginDescription
* const pd
, const String
& path
)
297 const String
firstSubFolder (path
.upToFirstOccurrenceOf ("/", false, false));
298 const String
remainingPath (path
.fromFirstOccurrenceOf ("/", false, false));
300 for (int i
= subFolders
.size(); --i
>= 0;)
302 if (subFolders
.getUnchecked(i
)->folder
.equalsIgnoreCase (firstSubFolder
))
304 subFolders
.getUnchecked(i
)->addPlugin (pd
, remainingPath
);
309 PluginFilesystemTree
* const newFolder
= new PluginFilesystemTree();
310 newFolder
->folder
= firstSubFolder
;
311 subFolders
.add (newFolder
);
313 newFolder
->addPlugin (pd
, remainingPath
);
317 // removes any deeply nested folders that don't contain any actual plugins
320 for (int i
= subFolders
.size(); --i
>= 0;)
322 PluginFilesystemTree
* const sub
= subFolders
.getUnchecked(i
);
326 if (sub
->plugins
.size() == 0)
328 for (int j
= 0; j
< sub
->subFolders
.size(); ++j
)
329 subFolders
.add (sub
->subFolders
.getUnchecked(j
));
331 sub
->subFolders
.clear (false);
332 subFolders
.remove (i
);
338 void buildTree (const Array
<PluginDescription
*>& allPlugins
)
340 for (int i
= 0; i
< allPlugins
.size(); ++i
)
342 String
path (allPlugins
.getUnchecked(i
)
343 ->fileOrIdentifier
.replaceCharacter ('\\', '/')
344 .upToLastOccurrenceOf ("/", false, false));
346 if (path
.substring (1, 2) == ":")
347 path
= path
.substring (2);
349 addPlugin (allPlugins
.getUnchecked(i
), path
);
355 void addToMenu (PopupMenu
& m
, const OwnedArray
<PluginDescription
>& allPlugins
) const
358 for (i
= 0; i
< subFolders
.size(); ++i
)
360 const PluginFilesystemTree
* const sub
= subFolders
.getUnchecked(i
);
363 sub
->addToMenu (subMenu
, allPlugins
);
366 // avoid the special AU formatting nonsense on Mac..
367 m
.addSubMenu (sub
->folder
.fromFirstOccurrenceOf (":", false, false), subMenu
);
369 m
.addSubMenu (sub
->folder
, subMenu
);
373 for (i
= 0; i
< plugins
.size(); ++i
)
375 PluginDescription
* const plugin
= plugins
.getUnchecked(i
);
377 m
.addItem (allPlugins
.indexOf (plugin
) + menuIdBase
,
378 plugin
->name
, true, false);
383 //==============================================================================
384 void KnownPluginList::addToMenu (PopupMenu
& menu
, const SortMethod sortMethod
) const
386 Array
<PluginDescription
*> sorted
;
390 sorter
.method
= sortMethod
;
392 for (int i
= 0; i
< types
.size(); ++i
)
393 sorted
.addSorted (sorter
, types
.getUnchecked(i
));
396 if (sortMethod
== sortByCategory
397 || sortMethod
== sortByManufacturer
)
399 String lastSubMenuName
;
402 for (int i
= 0; i
< sorted
.size(); ++i
)
404 const PluginDescription
* const pd
= sorted
.getUnchecked(i
);
405 String
thisSubMenuName (sortMethod
== sortByCategory
? pd
->category
406 : pd
->manufacturerName
);
408 if (! thisSubMenuName
.containsNonWhitespaceChars())
409 thisSubMenuName
= "Other";
411 if (thisSubMenuName
!= lastSubMenuName
)
413 if (sub
.getNumItems() > 0)
415 menu
.addSubMenu (lastSubMenuName
, sub
);
419 lastSubMenuName
= thisSubMenuName
;
422 sub
.addItem (types
.indexOf (pd
) + menuIdBase
, pd
->name
, true, false);
425 if (sub
.getNumItems() > 0)
426 menu
.addSubMenu (lastSubMenuName
, sub
);
428 else if (sortMethod
== sortByFileSystemLocation
)
430 PluginFilesystemTree root
;
431 root
.buildTree (sorted
);
432 root
.addToMenu (menu
, types
);
436 for (int i
= 0; i
< sorted
.size(); ++i
)
438 const PluginDescription
* const pd
= sorted
.getUnchecked(i
);
439 menu
.addItem (types
.indexOf (pd
) + menuIdBase
, pd
->name
, true, false);
444 int KnownPluginList::getIndexChosenByMenu (const int menuResultCode
) const
446 const int i
= menuResultCode
- menuIdBase
;
448 return isPositiveAndBelow (i
, types
.size()) ? i
: -1;