1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    By using JUCE, you agree to the terms of both the JUCE 6 End-User License
11    Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
12 
13    End User License Agreement: www.juce.com/juce-6-licence
14    Privacy Policy: www.juce.com/juce-privacy-policy
15 
16    Or: You may also use this code under the terms of the GPL v3 (see
17    www.gnu.org/licenses).
18 
19    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21    DISCLAIMED.
22 
23   ==============================================================================
24 */
25 
26 #include "../Application/jucer_Headers.h"
27 #include "jucer_Project.h"
28 #include "../ProjectSaving/jucer_ProjectSaver.h"
29 #include "../Application/jucer_Application.h"
30 #include "../LiveBuildEngine/jucer_CompileEngineSettings.h"
31 
32 //==============================================================================
ProjectFileModificationPoller(Project & p)33 Project::ProjectFileModificationPoller::ProjectFileModificationPoller (Project& p)
34     : project (p)
35 {
36     startTimer (250);
37 }
38 
reset()39 void Project::ProjectFileModificationPoller::reset()
40 {
41     project.removeProjectMessage (ProjectMessages::Ids::jucerFileModified);
42     pending = false;
43 
44     startTimer (250);
45 }
46 
timerCallback()47 void Project::ProjectFileModificationPoller::timerCallback()
48 {
49     if (project.updateCachedFileState() && ! pending)
50     {
51          project.addProjectMessage (ProjectMessages::Ids::jucerFileModified,
52                                     { { "Save current state", [this] { resaveProject(); } },
53                                       { "Re-load from disk",  [this] { reloadProjectFromDisk(); } },
54                                       { "Ignore",             [this] { reset(); } } });
55 
56          stopTimer();
57          pending = true;
58     }
59 }
60 
reloadProjectFromDisk()61 void Project::ProjectFileModificationPoller::reloadProjectFromDisk()
62 {
63     auto oldTemporaryDirectory = project.getTemporaryDirectory();
64     auto projectFile = project.getFile();
65 
66     MessageManager::callAsync ([oldTemporaryDirectory, projectFile]
67     {
68         if (auto* mw = ProjucerApplication::getApp().mainWindowList.getMainWindowForFile (projectFile))
69         {
70             mw->closeCurrentProject (OpenDocumentManager::SaveIfNeeded::no);
71             mw->openFile (projectFile);
72 
73             if (oldTemporaryDirectory != File())
74                 if (auto* newProject = mw->getProject())
75                     newProject->setTemporaryDirectory (oldTemporaryDirectory);
76         }
77     });
78 }
79 
resaveProject()80 void Project::ProjectFileModificationPoller::resaveProject()
81 {
82     reset();
83     project.saveProject();
84 }
85 
86 //==============================================================================
Project(const File & f)87 Project::Project (const File& f)
88     : FileBasedDocument (projectFileExtension,
89                          String ("*") + projectFileExtension,
90                          "Choose a Jucer project to load",
91                          "Save Jucer project")
92 {
93     Logger::writeToLog ("Loading project: " + f.getFullPathName());
94 
95     setFile (f);
96 
97     initialiseProjectValues();
98     initialiseMainGroup();
99     initialiseAudioPluginValues();
100 
101     setChangedFlag (false);
102     updateCachedFileState();
103 
104     auto& app = ProjucerApplication::getApp();
105 
106     if (! app.isRunningCommandLine)
107         app.getLicenseController().addListener (this);
108 
109     app.getJUCEPathModulesList().addListener (this);
110     app.getUserPathsModulesList().addListener (this);
111 
112     updateJUCEPathWarning();
113     getGlobalProperties().addChangeListener (this);
114 
115     if (! app.isRunningCommandLine && app.isAutomaticVersionCheckingEnabled())
116         LatestVersionCheckerAndUpdater::getInstance()->checkForNewVersion (true);
117 }
118 
~Project()119 Project::~Project()
120 {
121     projectRoot.removeListener (this);
122     getGlobalProperties().removeChangeListener (this);
123 
124     auto& app = ProjucerApplication::getApp();
125 
126     app.openDocumentManager.closeAllDocumentsUsingProject (*this, OpenDocumentManager::SaveIfNeeded::no);
127 
128     if (! app.isRunningCommandLine)
129         app.getLicenseController().removeListener (this);
130 
131     app.getJUCEPathModulesList().removeListener (this);
132     app.getUserPathsModulesList().removeListener (this);
133 }
134 
135 const char* Project::projectFileExtension = ".jucer";
136 
137 //==============================================================================
setTitle(const String & newTitle)138 void Project::setTitle (const String& newTitle)
139 {
140     projectNameValue = newTitle;
141 
142     updateTitleDependencies();
143 }
144 
updateTitleDependencies()145 void Project::updateTitleDependencies()
146 {
147     auto projectName = getProjectNameString();
148 
149     getMainGroup().getNameValue() = projectName;
150 
151     pluginNameValue.          setDefault (projectName);
152     pluginDescriptionValue.   setDefault (projectName);
153     bundleIdentifierValue.    setDefault (getDefaultBundleIdentifierString());
154     pluginAUExportPrefixValue.setDefault (build_tools::makeValidIdentifier (projectName, false, true, false) + "AU");
155     pluginAAXIdentifierValue. setDefault (getDefaultAAXIdentifierString());
156 }
157 
getDocumentTitle()158 String Project::getDocumentTitle()
159 {
160     return getProjectNameString();
161 }
162 
updateCompanyNameDependencies()163 void Project::updateCompanyNameDependencies()
164 {
165     bundleIdentifierValue.setDefault    (getDefaultBundleIdentifierString());
166     pluginAAXIdentifierValue.setDefault (getDefaultAAXIdentifierString());
167     pluginManufacturerValue.setDefault  (getDefaultPluginManufacturerString());
168 
169     updateLicenseWarning();
170 }
171 
updateProjectSettings()172 void Project::updateProjectSettings()
173 {
174     projectRoot.setProperty (Ids::name, getDocumentTitle(), nullptr);
175 }
176 
setCppVersionFromOldExporterSettings()177 bool Project::setCppVersionFromOldExporterSettings()
178 {
179     auto highestLanguageStandard = -1;
180 
181     for (ExporterIterator exporter (*this); exporter.next();)
182     {
183         if (exporter->isXcode()) // cpp version was per-build configuration for xcode exporters
184         {
185             for (ProjectExporter::ConfigIterator config (*exporter); config.next();)
186             {
187                 auto cppLanguageStandard = config->getValue (Ids::cppLanguageStandard).getValue();
188 
189                 if (cppLanguageStandard != var())
190                 {
191                     auto versionNum = cppLanguageStandard.toString().getLastCharacters (2).getIntValue();
192 
193                     if (versionNum > highestLanguageStandard)
194                         highestLanguageStandard = versionNum;
195                 }
196             }
197         }
198         else
199         {
200             auto cppLanguageStandard = exporter->getSetting (Ids::cppLanguageStandard).getValue();
201 
202             if (cppLanguageStandard != var())
203             {
204                 if (cppLanguageStandard.toString().containsIgnoreCase ("latest"))
205                 {
206                     cppStandardValue = Project::getCppStandardVars().getLast();
207                     return true;
208                 }
209 
210                 auto versionNum = cppLanguageStandard.toString().getLastCharacters (2).getIntValue();
211 
212                 if (versionNum > highestLanguageStandard)
213                     highestLanguageStandard = versionNum;
214             }
215         }
216     }
217 
218     if (highestLanguageStandard != -1 && highestLanguageStandard >= 11)
219     {
220         cppStandardValue = highestLanguageStandard;
221         return true;
222     }
223 
224     return false;
225 }
226 
updateDeprecatedProjectSettings()227 void Project::updateDeprecatedProjectSettings()
228 {
229     for (ExporterIterator exporter (*this); exporter.next();)
230         exporter->updateDeprecatedSettings();
231 }
232 
updateDeprecatedProjectSettingsInteractively()233 void Project::updateDeprecatedProjectSettingsInteractively()
234 {
235     jassert (! ProjucerApplication::getApp().isRunningCommandLine);
236 
237     for (ExporterIterator exporter (*this); exporter.next();)
238         exporter->updateDeprecatedSettingsInteractively();
239 }
240 
initialiseMainGroup()241 void Project::initialiseMainGroup()
242 {
243     // Create main file group if missing
244     if (! projectRoot.getChildWithName (Ids::MAINGROUP).isValid())
245     {
246         Item mainGroup (*this, ValueTree (Ids::MAINGROUP), false);
247         projectRoot.addChild (mainGroup.state, 0, nullptr);
248     }
249 
250     getMainGroup().initialiseMissingProperties();
251 }
252 
initialiseProjectValues()253 void Project::initialiseProjectValues()
254 {
255     projectNameValue.referTo         (projectRoot, Ids::name,                getUndoManager(), "JUCE Project");
256     projectUIDValue.referTo          (projectRoot, Ids::ID,                  getUndoManager(), createAlphaNumericUID());
257 
258     if (projectUIDValue.isUsingDefault())
259         projectUIDValue = projectUIDValue.getDefault();
260 
261     projectLineFeedValue.referTo     (projectRoot, Ids::projectLineFeed,     getUndoManager(), "\r\n");
262 
263     companyNameValue.referTo         (projectRoot, Ids::companyName,         getUndoManager());
264     companyCopyrightValue.referTo    (projectRoot, Ids::companyCopyright,    getUndoManager());
265     companyWebsiteValue.referTo      (projectRoot, Ids::companyWebsite,      getUndoManager());
266     companyEmailValue.referTo        (projectRoot, Ids::companyEmail,        getUndoManager());
267 
268     projectTypeValue.referTo         (projectRoot, Ids::projectType,         getUndoManager(), build_tools::ProjectType_GUIApp::getTypeName());
269     versionValue.referTo             (projectRoot, Ids::version,             getUndoManager(), "1.0.0");
270     bundleIdentifierValue.referTo    (projectRoot, Ids::bundleIdentifier,    getUndoManager(), getDefaultBundleIdentifierString());
271 
272     displaySplashScreenValue.referTo (projectRoot, Ids::displaySplashScreen, getUndoManager(), false);
273     splashScreenColourValue.referTo  (projectRoot, Ids::splashScreenColour,  getUndoManager(), "Dark");
274 
275     useAppConfigValue.referTo             (projectRoot, Ids::useAppConfig,                  getUndoManager(), true);
276     addUsingNamespaceToJuceHeader.referTo (projectRoot, Ids::addUsingNamespaceToJuceHeader, getUndoManager(), true);
277 
278     cppStandardValue.referTo       (projectRoot, Ids::cppLanguageStandard, getUndoManager(), "14");
279 
280     headerSearchPathsValue.referTo   (projectRoot, Ids::headerPath, getUndoManager());
281     preprocessorDefsValue.referTo    (projectRoot, Ids::defines,    getUndoManager());
282     userNotesValue.referTo           (projectRoot, Ids::userNotes,  getUndoManager());
283 
284     maxBinaryFileSizeValue.referTo   (projectRoot, Ids::maxBinaryFileSize,         getUndoManager(), 10240 * 1024);
285 
286     // this is here for backwards compatibility with old projects using the incorrect id
287     if (projectRoot.hasProperty ("includeBinaryInAppConfig"))
288          includeBinaryDataInJuceHeaderValue.referTo (projectRoot, "includeBinaryInAppConfig", getUndoManager(), true);
289     else
290         includeBinaryDataInJuceHeaderValue.referTo (projectRoot, Ids::includeBinaryInJuceHeader, getUndoManager(), true);
291 
292     binaryDataNamespaceValue.referTo (projectRoot, Ids::binaryDataNamespace, getUndoManager(), "BinaryData");
293 
294     compilerFlagSchemesValue.referTo (projectRoot, Ids::compilerFlagSchemes, getUndoManager(), Array<var>(), ",");
295 
296     postExportShellCommandPosixValue.referTo (projectRoot, Ids::postExportShellCommandPosix, getUndoManager());
297     postExportShellCommandWinValue.referTo   (projectRoot, Ids::postExportShellCommandWin,   getUndoManager());
298 }
299 
initialiseAudioPluginValues()300 void Project::initialiseAudioPluginValues()
301 {
302     auto makeValid4CC = [] (const String& seed)
303     {
304         auto s = build_tools::makeValidIdentifier (seed, false, true, false) + "xxxx";
305 
306         return s.substring (0, 1).toUpperCase()
307              + s.substring (1, 4).toLowerCase();
308     };
309 
310     pluginFormatsValue.referTo               (projectRoot, Ids::pluginFormats,              getUndoManager(),
311                                               Array<var> (Ids::buildVST3.toString(), Ids::buildAU.toString(), Ids::buildStandalone.toString()), ",");
312     pluginCharacteristicsValue.referTo       (projectRoot, Ids::pluginCharacteristicsValue, getUndoManager(), Array<var> (), ",");
313 
314     pluginNameValue.referTo                  (projectRoot, Ids::pluginName,                 getUndoManager(), getProjectNameString());
315     pluginDescriptionValue.referTo           (projectRoot, Ids::pluginDesc,                 getUndoManager(), getProjectNameString());
316     pluginManufacturerValue.referTo          (projectRoot, Ids::pluginManufacturer,         getUndoManager(), getDefaultPluginManufacturerString());
317     pluginManufacturerCodeValue.referTo      (projectRoot, Ids::pluginManufacturerCode,     getUndoManager(), "Manu");
318     pluginCodeValue.referTo                  (projectRoot, Ids::pluginCode,                 getUndoManager(), makeValid4CC (getProjectUIDString() + getProjectUIDString()));
319     pluginChannelConfigsValue.referTo        (projectRoot, Ids::pluginChannelConfigs,       getUndoManager());
320     pluginAAXIdentifierValue.referTo         (projectRoot, Ids::aaxIdentifier,              getUndoManager(), getDefaultAAXIdentifierString());
321     pluginAUExportPrefixValue.referTo        (projectRoot, Ids::pluginAUExportPrefix,       getUndoManager(),
322                                               build_tools::makeValidIdentifier (getProjectNameString(), false, true, false) + "AU");
323 
324     pluginAUMainTypeValue.referTo            (projectRoot, Ids::pluginAUMainType,           getUndoManager(), getDefaultAUMainTypes(),    ",");
325     pluginAUSandboxSafeValue.referTo         (projectRoot, Ids::pluginAUIsSandboxSafe,      getUndoManager(), false);
326     pluginVSTCategoryValue.referTo           (projectRoot, Ids::pluginVSTCategory,          getUndoManager(), getDefaultVSTCategories(),  ",");
327     pluginVST3CategoryValue.referTo          (projectRoot, Ids::pluginVST3Category,         getUndoManager(), getDefaultVST3Categories(), ",");
328     pluginRTASCategoryValue.referTo          (projectRoot, Ids::pluginRTASCategory,         getUndoManager(), getDefaultRTASCategories(), ",");
329     pluginAAXCategoryValue.referTo           (projectRoot, Ids::pluginAAXCategory,          getUndoManager(), getDefaultAAXCategories(),  ",");
330 
331     pluginVSTNumMidiInputsValue.referTo      (projectRoot, Ids::pluginVSTNumMidiInputs,     getUndoManager(), 16);
332     pluginVSTNumMidiOutputsValue.referTo     (projectRoot, Ids::pluginVSTNumMidiOutputs,    getUndoManager(), 16);
333 }
334 
updateOldStyleConfigList()335 void Project::updateOldStyleConfigList()
336 {
337     auto deprecatedConfigsList = projectRoot.getChildWithName (Ids::CONFIGURATIONS);
338 
339     if (deprecatedConfigsList.isValid())
340     {
341         projectRoot.removeChild (deprecatedConfigsList, nullptr);
342 
343         for (ExporterIterator exporter (*this); exporter.next();)
344         {
345             if (exporter->getNumConfigurations() == 0)
346             {
347                 auto newConfigs = deprecatedConfigsList.createCopy();
348 
349                 if (! exporter->isXcode())
350                 {
351                     for (auto j = newConfigs.getNumChildren(); --j >= 0;)
352                     {
353                         auto config = newConfigs.getChild (j);
354 
355                         config.removeProperty (Ids::osxSDK,           nullptr);
356                         config.removeProperty (Ids::osxCompatibility, nullptr);
357                         config.removeProperty (Ids::osxArchitecture,  nullptr);
358                     }
359                 }
360 
361                 exporter->settings.addChild (newConfigs, 0, nullptr);
362             }
363         }
364     }
365 }
366 
moveOldPropertyFromProjectToAllExporters(Identifier name)367 void Project::moveOldPropertyFromProjectToAllExporters (Identifier name)
368 {
369     if (projectRoot.hasProperty (name))
370     {
371         for (ExporterIterator exporter (*this); exporter.next();)
372             exporter->settings.setProperty (name, projectRoot [name], nullptr);
373 
374         projectRoot.removeProperty (name, nullptr);
375     }
376 }
377 
removeDefunctExporters()378 void Project::removeDefunctExporters()
379 {
380     auto exporters = projectRoot.getChildWithName (Ids::EXPORTFORMATS);
381 
382     StringPairArray oldExporters;
383     oldExporters.set ("ANDROID", "Android Ant Exporter");
384     oldExporters.set ("MSVC6",   "MSVC6");
385     oldExporters.set ("VS2010",  "Visual Studio 2010");
386     oldExporters.set ("VS2012",  "Visual Studio 2012");
387     oldExporters.set ("VS2013",  "Visual Studio 2013");
388 
389     for (auto& key : oldExporters.getAllKeys())
390     {
391         auto oldExporter = exporters.getChildWithName (key);
392 
393         if (oldExporter.isValid())
394         {
395             if (ProjucerApplication::getApp().isRunningCommandLine)
396                 std::cout <<  "WARNING! The " + oldExporters[key]  + " Exporter is deprecated. The exporter will be removed from this project." << std::endl;
397             else
398                 AlertWindow::showMessageBox (AlertWindow::WarningIcon,
399                                              TRANS (oldExporters[key]),
400                                              TRANS ("The " + oldExporters[key]  + " Exporter is deprecated. The exporter will be removed from this project."));
401 
402             exporters.removeChild (oldExporter, nullptr);
403         }
404     }
405 }
406 
updateOldModulePaths()407 void Project::updateOldModulePaths()
408 {
409     for (ExporterIterator exporter (*this); exporter.next();)
410         exporter->updateOldModulePaths();
411 }
412 
getLegacyPluginFormatIdentifiers()413 Array<Identifier> Project::getLegacyPluginFormatIdentifiers() noexcept
414 {
415     static Array<Identifier> legacyPluginFormatIdentifiers { Ids::buildVST, Ids::buildVST3, Ids::buildAU, Ids::buildAUv3,
416                                                              Ids::buildRTAS, Ids::buildAAX, Ids::buildStandalone, Ids::enableIAA };
417 
418     return legacyPluginFormatIdentifiers;
419 }
420 
getLegacyPluginCharacteristicsIdentifiers()421 Array<Identifier> Project::getLegacyPluginCharacteristicsIdentifiers() noexcept
422 {
423     static Array<Identifier> legacyPluginCharacteristicsIdentifiers { Ids::pluginIsSynth, Ids::pluginWantsMidiIn, Ids::pluginProducesMidiOut,
424                                                                       Ids::pluginIsMidiEffectPlugin, Ids::pluginEditorRequiresKeys, Ids::pluginRTASDisableBypass,
425                                                                       Ids::pluginRTASDisableMultiMono, Ids::pluginAAXDisableBypass, Ids::pluginAAXDisableMultiMono };
426 
427     return legacyPluginCharacteristicsIdentifiers;
428 }
429 
coalescePluginFormatValues()430 void Project::coalescePluginFormatValues()
431 {
432     Array<var> formatsToBuild;
433 
434     for (auto& formatIdentifier : getLegacyPluginFormatIdentifiers())
435     {
436         if (projectRoot.getProperty (formatIdentifier, false))
437             formatsToBuild.add (formatIdentifier.toString());
438     }
439 
440     if (formatsToBuild.size() > 0)
441     {
442         if (pluginFormatsValue.isUsingDefault())
443         {
444             pluginFormatsValue = formatsToBuild;
445         }
446         else
447         {
448             auto formatVar = pluginFormatsValue.get();
449 
450             if (auto* arr = formatVar.getArray())
451                 arr->addArray (formatsToBuild);
452         }
453 
454         shouldWriteLegacyPluginFormatSettings = true;
455     }
456 }
457 
coalescePluginCharacteristicsValues()458 void Project::coalescePluginCharacteristicsValues()
459 {
460     Array<var> pluginCharacteristics;
461 
462     for (auto& characteristicIdentifier : getLegacyPluginCharacteristicsIdentifiers())
463     {
464         if (projectRoot.getProperty (characteristicIdentifier, false))
465             pluginCharacteristics.add (characteristicIdentifier.toString());
466     }
467 
468     if (pluginCharacteristics.size() > 0)
469     {
470         pluginCharacteristicsValue = pluginCharacteristics;
471         shouldWriteLegacyPluginCharacteristicsSettings = true;
472     }
473 }
474 
updatePluginCategories()475 void Project::updatePluginCategories()
476 {
477     {
478         auto aaxCategory = projectRoot.getProperty (Ids::pluginAAXCategory, {}).toString();
479 
480         if (getAllAAXCategoryVars().contains (aaxCategory))
481             pluginAAXCategoryValue = aaxCategory;
482         else if (getAllAAXCategoryStrings().contains (aaxCategory))
483             pluginAAXCategoryValue = Array<var> (getAllAAXCategoryVars()[getAllAAXCategoryStrings().indexOf (aaxCategory)]);
484     }
485 
486     {
487         auto rtasCategory = projectRoot.getProperty (Ids::pluginRTASCategory, {}).toString();
488 
489         if (getAllRTASCategoryVars().contains (rtasCategory))
490             pluginRTASCategoryValue = rtasCategory;
491         else if (getAllRTASCategoryStrings().contains (rtasCategory))
492             pluginRTASCategoryValue = Array<var> (getAllRTASCategoryVars()[getAllRTASCategoryStrings().indexOf (rtasCategory)]);
493     }
494 
495     {
496         auto vstCategory = projectRoot.getProperty (Ids::pluginVSTCategory, {}).toString();
497 
498         if (vstCategory.isNotEmpty() && getAllVSTCategoryStrings().contains (vstCategory))
499             pluginVSTCategoryValue = Array<var> (vstCategory);
500         else
501             pluginVSTCategoryValue.resetToDefault();
502     }
503 
504     {
505         auto auMainType = projectRoot.getProperty (Ids::pluginAUMainType, {}).toString();
506 
507         if (auMainType.isNotEmpty())
508         {
509             if (getAllAUMainTypeVars().contains (auMainType))
510                 pluginAUMainTypeValue = Array<var> (auMainType);
511             else if (getAllAUMainTypeVars().contains (auMainType.quoted ('\'')))
512                 pluginAUMainTypeValue = Array<var> (auMainType.quoted ('\''));
513             else if (getAllAUMainTypeStrings().contains (auMainType))
514                 pluginAUMainTypeValue = Array<var> (getAllAUMainTypeVars()[getAllAUMainTypeStrings().indexOf (auMainType)]);
515         }
516         else
517         {
518             pluginAUMainTypeValue.resetToDefault();
519         }
520     }
521 }
522 
writeLegacyPluginFormatSettings()523 void Project::writeLegacyPluginFormatSettings()
524 {
525     if (pluginFormatsValue.isUsingDefault())
526     {
527         for (auto& formatIdentifier : getLegacyPluginFormatIdentifiers())
528             projectRoot.removeProperty (formatIdentifier, nullptr);
529     }
530     else
531     {
532         auto formatVar = pluginFormatsValue.get();
533 
534         if (auto* arr = formatVar.getArray())
535         {
536             for (auto& formatIdentifier : getLegacyPluginFormatIdentifiers())
537                 projectRoot.setProperty (formatIdentifier, arr->contains (formatIdentifier.toString()), nullptr);
538         }
539     }
540 }
541 
writeLegacyPluginCharacteristicsSettings()542 void Project::writeLegacyPluginCharacteristicsSettings()
543 {
544     if (pluginFormatsValue.isUsingDefault())
545     {
546         for (auto& characteristicIdentifier : getLegacyPluginCharacteristicsIdentifiers())
547             projectRoot.removeProperty (characteristicIdentifier, nullptr);
548     }
549     else
550     {
551         auto characteristicsVar = pluginCharacteristicsValue.get();
552 
553         if (auto* arr = characteristicsVar.getArray())
554         {
555             for (auto& characteristicIdentifier : getLegacyPluginCharacteristicsIdentifiers())
556                 projectRoot.setProperty (characteristicIdentifier, arr->contains (characteristicIdentifier.toString()), nullptr);
557         }
558     }
559 }
560 
561 //==============================================================================
getVersionElement(StringRef v,int index)562 static int getVersionElement (StringRef v, int index)
563 {
564     StringArray parts = StringArray::fromTokens (v, "., ", {});
565 
566     return parts [parts.size() - index - 1].getIntValue();
567 }
568 
getJuceVersion(const String & v)569 static int getJuceVersion (const String& v)
570 {
571     return getVersionElement (v, 2) * 100000
572          + getVersionElement (v, 1) * 1000
573          + getVersionElement (v, 0);
574 }
575 
getBuiltJuceVersion()576 static constexpr int getBuiltJuceVersion()
577 {
578     return JUCE_MAJOR_VERSION * 100000
579          + JUCE_MINOR_VERSION * 1000
580          + JUCE_BUILDNUMBER;
581 }
582 
583 //==============================================================================
lastDocumentOpenedSingleton()584 static File& lastDocumentOpenedSingleton()
585 {
586     static File lastDocumentOpened;
587     return lastDocumentOpened;
588 }
589 
getLastDocumentOpened()590 File Project::getLastDocumentOpened()                   { return lastDocumentOpenedSingleton(); }
setLastDocumentOpened(const File & file)591 void Project::setLastDocumentOpened (const File& file)  { lastDocumentOpenedSingleton() = file; }
592 
registerRecentFile(const File & file)593 static void registerRecentFile (const File& file)
594 {
595     RecentlyOpenedFilesList::registerRecentFileNatively (file);
596     getAppSettings().recentFiles.addFile (file);
597     getAppSettings().flush();
598 }
599 
forgetRecentFile(const File & file)600 static void forgetRecentFile (const File& file)
601 {
602     RecentlyOpenedFilesList::forgetRecentFileNatively (file);
603     getAppSettings().recentFiles.removeFile (file);
604     getAppSettings().flush();
605 }
606 
607 //==============================================================================
loadDocument(const File & file)608 Result Project::loadDocument (const File& file)
609 {
610     auto xml = parseXMLIfTagMatches (file, Ids::JUCERPROJECT.toString());
611 
612     if (xml == nullptr)
613         return Result::fail ("Not a valid Jucer project!");
614 
615     auto newTree = ValueTree::fromXml (*xml);
616 
617     if (! newTree.hasType (Ids::JUCERPROJECT))
618         return Result::fail ("The document contains errors and couldn't be parsed!");
619 
620     registerRecentFile (file);
621 
622     enabledModulesList.reset();
623 
624     projectRoot = newTree;
625     projectRoot.addListener (this);
626 
627     initialiseProjectValues();
628     initialiseMainGroup();
629     initialiseAudioPluginValues();
630 
631     coalescePluginFormatValues();
632     coalescePluginCharacteristicsValues();
633     updatePluginCategories();
634 
635     parsedPreprocessorDefs = parsePreprocessorDefs (preprocessorDefsValue.get());
636 
637     removeDefunctExporters();
638     updateOldModulePaths();
639     updateOldStyleConfigList();
640     moveOldPropertyFromProjectToAllExporters (Ids::bigIcon);
641     moveOldPropertyFromProjectToAllExporters (Ids::smallIcon);
642     getEnabledModules().sortAlphabetically();
643 
644     compileEngineSettings.reset (new CompileEngineSettings (projectRoot));
645 
646     rescanExporterPathModules (! ProjucerApplication::getApp().isRunningCommandLine);
647     exporterPathsModulesList.addListener (this);
648 
649     if (cppStandardValue.isUsingDefault())
650         setCppVersionFromOldExporterSettings();
651 
652     updateDeprecatedProjectSettings();
653 
654     setChangedFlag (false);
655 
656     updateExporterWarnings();
657     updateLicenseWarning();
658 
659     return Result::ok();
660 }
661 
saveDocument(const File & file)662 Result Project::saveDocument (const File& file)
663 {
664     jassert (file == getFile());
665     ignoreUnused (file);
666 
667     return saveProject();
668 }
669 
saveProject(ProjectExporter * exporterToSave)670 Result Project::saveProject (ProjectExporter* exporterToSave)
671 {
672     if (isSaveAndExportDisabled())
673         return Result::fail ("Save and export is disabled.");
674 
675     if (isSaving)
676         return Result::ok();
677 
678     if (isTemporaryProject())
679     {
680         saveAndMoveTemporaryProject (false);
681         return Result::ok();
682     }
683 
684     updateProjectSettings();
685 
686     if (! ProjucerApplication::getApp().isRunningCommandLine)
687     {
688         ProjucerApplication::getApp().openDocumentManager.saveAll();
689 
690         if (! isTemporaryProject())
691             registerRecentFile (getFile());
692     }
693 
694     const ScopedValueSetter<bool> vs (isSaving, true, false);
695 
696     ProjectSaver saver (*this);
697     return saver.save (exporterToSave);
698 }
699 
openProjectInIDE(ProjectExporter & exporterToOpen,bool saveFirst)700 Result Project::openProjectInIDE (ProjectExporter& exporterToOpen, bool saveFirst)
701 {
702     for (ExporterIterator exporter (*this); exporter.next();)
703     {
704         if (exporter->canLaunchProject() && exporter->getUniqueName() == exporterToOpen.getUniqueName())
705         {
706             if (isTemporaryProject())
707             {
708                 saveAndMoveTemporaryProject (true);
709                 return Result::ok();
710             }
711 
712             if (saveFirst)
713             {
714                 auto result = saveProject();
715 
716                 if (! result.wasOk())
717                     return result;
718             }
719 
720             // Workaround for a bug where Xcode thinks the project is invalid if opened immediately
721             // after writing
722             if (saveFirst && exporter->isXcode())
723                 Thread::sleep (1000);
724 
725             exporter->launchProject();
726         }
727     }
728 
729     return Result::ok();
730 }
731 
saveResourcesOnly()732 Result Project::saveResourcesOnly()
733 {
734     ProjectSaver saver (*this);
735     return saver.saveResourcesOnly();
736 }
737 
hasIncompatibleLicenseTypeAndSplashScreenSetting() const738 bool Project::hasIncompatibleLicenseTypeAndSplashScreenSetting() const
739 {
740     auto companyName = companyNameValue.get().toString();
741     auto isJUCEProject = (companyName == "Raw Material Software Limited"
742                        || companyName == "JUCE"
743                        || companyName == "ROLI Ltd.");
744 
745     return ! ProjucerApplication::getApp().isRunningCommandLine && ! isJUCEProject && ! shouldDisplaySplashScreen()
746           && ! ProjucerApplication::getApp().getLicenseController().getCurrentState().canUnlockFullFeatures();
747 }
748 
isFileModificationCheckPending() const749 bool Project::isFileModificationCheckPending() const
750 {
751     return fileModificationPoller.isCheckPending();
752 }
753 
isSaveAndExportDisabled() const754 bool Project::isSaveAndExportDisabled() const
755 {
756     return ! ProjucerApplication::getApp().isRunningCommandLine
757            && (hasIncompatibleLicenseTypeAndSplashScreenSetting() || isFileModificationCheckPending());
758 }
759 
updateLicenseWarning()760 void Project::updateLicenseWarning()
761 {
762     if (hasIncompatibleLicenseTypeAndSplashScreenSetting())
763     {
764         ProjectMessages::MessageAction action;
765         auto currentLicenseState = ProjucerApplication::getApp().getLicenseController().getCurrentState();
766 
767         if (currentLicenseState.isSignedIn() && (! currentLicenseState.canUnlockFullFeatures() || currentLicenseState.isOldLicense()))
768             action = { "Upgrade", [] { URL ("https://juce.com/get-juce").launchInDefaultBrowser(); } };
769         else
770             action = { "Sign in", [this] { ProjucerApplication::getApp().mainWindowList.getMainWindowForFile (getFile())->showLoginFormOverlay(); } };
771 
772         addProjectMessage (ProjectMessages::Ids::incompatibleLicense,
773                            { std::move (action), { "Enable splash screen", [this] { displaySplashScreenValue = true; } } });
774     }
775     else
776     {
777         removeProjectMessage (ProjectMessages::Ids::incompatibleLicense);
778     }
779 }
780 
updateJUCEPathWarning()781 void Project::updateJUCEPathWarning()
782 {
783     if (ProjucerApplication::getApp().shouldPromptUserAboutIncorrectJUCEPath()
784         && ProjucerApplication::getApp().settings->isJUCEPathIncorrect())
785     {
786         auto dontAskAgain = [this]
787         {
788             ProjucerApplication::getApp().setShouldPromptUserAboutIncorrectJUCEPath (false);
789             removeProjectMessage (ProjectMessages::Ids::jucePath);
790         };
791 
792         addProjectMessage (ProjectMessages::Ids::jucePath,
793                            { { "Set path", [] { ProjucerApplication::getApp().showPathsWindow (true); } },
794                              { "Ignore", [this] { removeProjectMessage (ProjectMessages::Ids::jucePath); } },
795                              { "Don't ask again", std::move (dontAskAgain) } });
796     }
797     else
798     {
799         removeProjectMessage (ProjectMessages::Ids::jucePath);
800     }
801 }
802 
updateModuleWarnings()803 void Project::updateModuleWarnings()
804 {
805     auto& modules = getEnabledModules();
806 
807     bool cppStandard = false, missingDependencies = false, oldProjucer = false, moduleNotFound = false;
808 
809     for (auto moduleID : modules.getAllModules())
810     {
811         if (! cppStandard && modules.doesModuleHaveHigherCppStandardThanProject (moduleID))
812             cppStandard = true;
813 
814         if (! missingDependencies && ! modules.getExtraDependenciesNeeded (moduleID).isEmpty())
815             missingDependencies = true;
816 
817         auto info = modules.getModuleInfo (moduleID);
818 
819         if (! oldProjucer && (isJUCEModule (moduleID) && getJuceVersion (info.getVersion()) > getBuiltJuceVersion()))
820             oldProjucer = true;
821 
822         if (! moduleNotFound && ! info.isValid())
823             moduleNotFound = true;
824     }
825 
826     updateCppStandardWarning (cppStandard);
827     updateMissingModuleDependenciesWarning (missingDependencies);
828     updateOldProjucerWarning (oldProjucer);
829     updateModuleNotFoundWarning (moduleNotFound);
830 }
831 
updateExporterWarnings()832 void Project::updateExporterWarnings()
833 {
834     auto isClionPresent = [this]()
835     {
836         for (ExporterIterator exporter (*this); exporter.next();)
837             if (exporter->isCLion())
838                 return true;
839 
840         return false;
841     }();
842 
843     updateCLionWarning (isClionPresent);
844 }
845 
updateCppStandardWarning(bool showWarning)846 void Project::updateCppStandardWarning (bool showWarning)
847 {
848     if (showWarning)
849     {
850         auto removeModules = [this]
851         {
852             auto& modules = getEnabledModules();
853 
854             for (auto& module : modules.getModulesWithHigherCppStandardThanProject())
855                 modules.removeModule (module);
856         };
857 
858         auto updateCppStandard = [this]
859         {
860             cppStandardValue = getEnabledModules().getHighestModuleCppStandard();
861         };
862 
863         addProjectMessage (ProjectMessages::Ids::cppStandard,
864                            { { "Update project C++ standard" , std::move (updateCppStandard) },
865                              { "Remove module(s)", std::move (removeModules) } });
866     }
867     else
868     {
869         removeProjectMessage (ProjectMessages::Ids::cppStandard);
870     }
871 }
872 
updateMissingModuleDependenciesWarning(bool showWarning)873 void Project::updateMissingModuleDependenciesWarning (bool showWarning)
874 {
875     if (showWarning)
876     {
877         auto removeModules = [this]
878         {
879             auto& modules = getEnabledModules();
880 
881             for (auto& mod : modules.getModulesWithMissingDependencies())
882                 modules.removeModule (mod);
883         };
884 
885         auto addMissingDependencies = [this]
886         {
887             auto& modules = getEnabledModules();
888 
889             for (auto& mod : modules.getModulesWithMissingDependencies())
890                 modules.tryToFixMissingDependencies (mod);
891         };
892 
893         addProjectMessage (ProjectMessages::Ids::missingModuleDependencies,
894                            { { "Add missing dependencies", std::move (addMissingDependencies) },
895                              { "Remove module(s)", std::move (removeModules) } });
896     }
897     else
898     {
899         removeProjectMessage (ProjectMessages::Ids::missingModuleDependencies);
900     }
901 }
902 
updateOldProjucerWarning(bool showWarning)903 void Project::updateOldProjucerWarning (bool showWarning)
904 {
905     if (showWarning)
906         addProjectMessage (ProjectMessages::Ids::oldProjucer, {});
907     else
908         removeProjectMessage (ProjectMessages::Ids::oldProjucer);
909 }
910 
updateCLionWarning(bool showWarning)911 void Project::updateCLionWarning (bool showWarning)
912 {
913     if (showWarning)
914         addProjectMessage (ProjectMessages::Ids::cLion, {});
915     else
916         removeProjectMessage (ProjectMessages::Ids::cLion);
917 }
918 
updateModuleNotFoundWarning(bool showWarning)919 void Project::updateModuleNotFoundWarning (bool showWarning)
920 {
921     if (showWarning)
922         addProjectMessage (ProjectMessages::Ids::moduleNotFound, {});
923     else
924         removeProjectMessage (ProjectMessages::Ids::moduleNotFound);
925 }
926 
licenseStateChanged()927 void Project::licenseStateChanged()
928 {
929     updateLicenseWarning();
930 }
931 
changeListenerCallback(ChangeBroadcaster *)932 void Project::changeListenerCallback (ChangeBroadcaster*)
933 {
934     updateJUCEPathWarning();
935 }
936 
availableModulesChanged(AvailableModulesList * listThatHasChanged)937 void Project::availableModulesChanged (AvailableModulesList* listThatHasChanged)
938 {
939     if (listThatHasChanged == &ProjucerApplication::getApp().getJUCEPathModulesList())
940         updateJUCEPathWarning();
941 
942     updateModuleWarnings();
943 }
944 
addProjectMessage(const Identifier & messageToAdd,std::vector<ProjectMessages::MessageAction> && actions)945 void Project::addProjectMessage (const Identifier& messageToAdd,
946                                  std::vector<ProjectMessages::MessageAction>&& actions)
947 {
948     removeProjectMessage (messageToAdd);
949 
950     messageActions[messageToAdd] = std::move (actions);
951 
952     ValueTree child (messageToAdd);
953     child.setProperty (ProjectMessages::Ids::isVisible, true, nullptr);
954 
955     projectMessages.getChildWithName (ProjectMessages::getTypeForMessage (messageToAdd)).addChild (child, -1, nullptr);
956 }
957 
removeProjectMessage(const Identifier & messageToRemove)958 void Project::removeProjectMessage (const Identifier& messageToRemove)
959 {
960     auto subTree = projectMessages.getChildWithName (ProjectMessages::getTypeForMessage (messageToRemove));
961     auto child = subTree.getChildWithName (messageToRemove);
962 
963     if (child.isValid())
964         subTree.removeChild (child, nullptr);
965 
966     messageActions.erase (messageToRemove);
967 }
968 
getMessageActions(const Identifier & message)969 std::vector<ProjectMessages::MessageAction> Project::getMessageActions (const Identifier& message)
970 {
971     auto iter = messageActions.find (message);
972 
973     if (iter != messageActions.end())
974         return iter->second;
975 
976     jassertfalse;
977     return {};
978 }
979 
980 //==============================================================================
setTemporaryDirectory(const File & dir)981 void Project::setTemporaryDirectory (const File& dir) noexcept
982 {
983     tempDirectory = dir;
984 
985      // remove this file from the recent documents list as it is a temporary project
986     forgetRecentFile (getFile());
987 }
988 
saveAndMoveTemporaryProject(bool openInIDE)989 void Project::saveAndMoveTemporaryProject (bool openInIDE)
990 {
991     FileChooser fc ("Save Project");
992     fc.browseForDirectory();
993 
994     auto newParentDirectory = fc.getResult();
995 
996     if (! newParentDirectory.exists())
997         return;
998 
999     auto newDirectory = newParentDirectory.getChildFile (tempDirectory.getFileName());
1000     auto oldJucerFileName = getFile().getFileName();
1001 
1002     ProjectSaver saver (*this);
1003     saver.save();
1004 
1005     tempDirectory.copyDirectoryTo (newDirectory);
1006     tempDirectory.deleteRecursively();
1007     tempDirectory = File();
1008 
1009     // reload project from new location
1010     if (auto* window = ProjucerApplication::getApp().mainWindowList.getMainWindowForFile (getFile()))
1011     {
1012         Component::SafePointer<MainWindow> safeWindow (window);
1013 
1014         MessageManager::callAsync ([safeWindow, newDirectory, oldJucerFileName, openInIDE]() mutable
1015         {
1016             if (safeWindow != nullptr)
1017                 safeWindow->moveProject (newDirectory.getChildFile (oldJucerFileName),
1018                                          openInIDE ? MainWindow::OpenInIDE::yes
1019                                                    : MainWindow::OpenInIDE::no);
1020         });
1021     }
1022 }
1023 
1024 //==============================================================================
valueTreePropertyChanged(ValueTree & tree,const Identifier & property)1025 void Project::valueTreePropertyChanged (ValueTree& tree, const Identifier& property)
1026 {
1027     if (tree.getRoot() == tree)
1028     {
1029         if (property == Ids::name)
1030         {
1031             updateTitleDependencies();
1032         }
1033         else if (property == Ids::companyName)
1034         {
1035             updateCompanyNameDependencies();
1036         }
1037         else if (property == Ids::defines)
1038         {
1039             parsedPreprocessorDefs = parsePreprocessorDefs (preprocessorDefsValue.get());
1040         }
1041         else if (property == Ids::pluginFormats)
1042         {
1043             if (shouldWriteLegacyPluginFormatSettings)
1044                 writeLegacyPluginFormatSettings();
1045         }
1046         else if (property == Ids::pluginCharacteristicsValue)
1047         {
1048             pluginAUMainTypeValue.setDefault   (getDefaultAUMainTypes());
1049             pluginVSTCategoryValue.setDefault  (getDefaultVSTCategories());
1050             pluginVST3CategoryValue.setDefault (getDefaultVST3Categories());
1051             pluginRTASCategoryValue.setDefault (getDefaultRTASCategories());
1052             pluginAAXCategoryValue.setDefault  (getDefaultAAXCategories());
1053 
1054             if (shouldWriteLegacyPluginCharacteristicsSettings)
1055                 writeLegacyPluginCharacteristicsSettings();
1056         }
1057         else if (property == Ids::displaySplashScreen)
1058         {
1059             updateLicenseWarning();
1060         }
1061         else if (property == Ids::cppLanguageStandard)
1062         {
1063             updateModuleWarnings();
1064         }
1065 
1066         changed();
1067     }
1068 }
1069 
valueTreeChildAdded(ValueTree & parent,ValueTree & child)1070 void Project::valueTreeChildAdded (ValueTree& parent, ValueTree& child)
1071 {
1072     ignoreUnused (parent);
1073 
1074     if (child.getType() == Ids::MODULE)
1075         updateModuleWarnings();
1076     else if (parent.getType() == Ids::EXPORTFORMATS)
1077         updateExporterWarnings();
1078 
1079     changed();
1080 }
1081 
valueTreeChildRemoved(ValueTree & parent,ValueTree & child,int index)1082 void Project::valueTreeChildRemoved (ValueTree& parent, ValueTree& child, int index)
1083 {
1084     ignoreUnused (parent, index);
1085 
1086     if (child.getType() == Ids::MODULE)
1087         updateModuleWarnings();
1088     else if (parent.getType() == Ids::EXPORTFORMATS)
1089         updateExporterWarnings();
1090 
1091     changed();
1092 }
1093 
valueTreeChildOrderChanged(ValueTree &,int,int)1094 void Project::valueTreeChildOrderChanged (ValueTree&, int, int)
1095 {
1096     changed();
1097 }
1098 
1099 //==============================================================================
serialiseProjectXml(std::unique_ptr<XmlElement> xml) const1100 String Project::serialiseProjectXml (std::unique_ptr<XmlElement> xml) const
1101 {
1102     if (xml == nullptr)
1103         return {};
1104 
1105     XmlElement::TextFormat format;
1106     format.newLineChars = getProjectLineFeed().toRawUTF8();
1107     return xml->toString (format);
1108 }
1109 
updateCachedFileState()1110 bool Project::updateCachedFileState()
1111 {
1112     auto lastModificationTime = getFile().getLastModificationTime();
1113 
1114     if (lastModificationTime <= cachedFileState.first)
1115         return false;
1116 
1117     cachedFileState.first = lastModificationTime;
1118 
1119     auto serialisedFileContent = serialiseProjectXml (XmlDocument (getFile()).getDocumentElement());
1120 
1121     if (serialisedFileContent == cachedFileState.second)
1122         return false;
1123 
1124     cachedFileState.second = serialisedFileContent;
1125     return true;
1126 }
1127 
1128 //==============================================================================
resolveFilename(String filename) const1129 File Project::resolveFilename (String filename) const
1130 {
1131     if (filename.isEmpty())
1132         return {};
1133 
1134     filename = build_tools::replacePreprocessorDefs (getPreprocessorDefs(), filename);
1135 
1136    #if ! JUCE_WINDOWS
1137     if (filename.startsWith ("~"))
1138         return File::getSpecialLocation (File::userHomeDirectory).getChildFile (filename.trimCharactersAtStart ("~/"));
1139    #endif
1140 
1141     if (build_tools::isAbsolutePath (filename))
1142         return File::createFileWithoutCheckingPath (build_tools::currentOSStylePath (filename)); // (avoid assertions for windows-style paths)
1143 
1144     return getFile().getSiblingFile (build_tools::currentOSStylePath (filename));
1145 }
1146 
getRelativePathForFile(const File & file) const1147 String Project::getRelativePathForFile (const File& file) const
1148 {
1149     auto filename = file.getFullPathName();
1150 
1151     auto relativePathBase = getFile().getParentDirectory();
1152 
1153     auto p1 = relativePathBase.getFullPathName();
1154     auto p2 = file.getFullPathName();
1155 
1156     while (p1.startsWithChar (File::getSeparatorChar()))
1157         p1 = p1.substring (1);
1158 
1159     while (p2.startsWithChar (File::getSeparatorChar()))
1160         p2 = p2.substring (1);
1161 
1162     if (p1.upToFirstOccurrenceOf (File::getSeparatorString(), true, false)
1163           .equalsIgnoreCase (p2.upToFirstOccurrenceOf (File::getSeparatorString(), true, false)))
1164     {
1165         filename = build_tools::getRelativePathFrom (file, relativePathBase);
1166     }
1167 
1168     return filename;
1169 }
1170 
1171 //==============================================================================
getProjectType() const1172 const build_tools::ProjectType& Project::getProjectType() const
1173 {
1174     if (auto* type = build_tools::ProjectType::findType (getProjectTypeString()))
1175         return *type;
1176 
1177     auto* guiType = build_tools::ProjectType::findType (build_tools::ProjectType_GUIApp::getTypeName());
1178     jassert (guiType != nullptr);
1179     return *guiType;
1180 }
1181 
shouldBuildTargetType(build_tools::ProjectType::Target::Type targetType) const1182 bool Project::shouldBuildTargetType (build_tools::ProjectType::Target::Type targetType) const noexcept
1183 {
1184     auto& projectType = getProjectType();
1185 
1186     if (! projectType.supportsTargetType (targetType))
1187         return false;
1188 
1189     switch (targetType)
1190     {
1191         case build_tools::ProjectType::Target::VSTPlugIn:
1192             return shouldBuildVST();
1193         case build_tools::ProjectType::Target::VST3PlugIn:
1194             return shouldBuildVST3();
1195         case build_tools::ProjectType::Target::AAXPlugIn:
1196             return shouldBuildAAX();
1197         case build_tools::ProjectType::Target::RTASPlugIn:
1198             return shouldBuildRTAS();
1199         case build_tools::ProjectType::Target::AudioUnitPlugIn:
1200             return shouldBuildAU();
1201         case build_tools::ProjectType::Target::AudioUnitv3PlugIn:
1202             return shouldBuildAUv3();
1203         case build_tools::ProjectType::Target::StandalonePlugIn:
1204             return shouldBuildStandalonePlugin();
1205         case build_tools::ProjectType::Target::UnityPlugIn:
1206             return shouldBuildUnityPlugin();
1207         case build_tools::ProjectType::Target::AggregateTarget:
1208         case build_tools::ProjectType::Target::SharedCodeTarget:
1209             return projectType.isAudioPlugin();
1210         case build_tools::ProjectType::Target::unspecified:
1211             return false;
1212         case build_tools::ProjectType::Target::GUIApp:
1213         case build_tools::ProjectType::Target::ConsoleApp:
1214         case build_tools::ProjectType::Target::StaticLibrary:
1215         case build_tools::ProjectType::Target::DynamicLibrary:
1216         default:
1217             break;
1218     }
1219 
1220     return true;
1221 }
1222 
getTargetTypeFromFilePath(const File & file,bool returnSharedTargetIfNoValidSuffix)1223 build_tools::ProjectType::Target::Type Project::getTargetTypeFromFilePath (const File& file, bool returnSharedTargetIfNoValidSuffix)
1224 {
1225     auto path = file.getFullPathName();
1226     String pluginClientModuleName = "juce_audio_plugin_client";
1227 
1228     auto isInPluginClientSubdir = [&path, &pluginClientModuleName] (StringRef subDir)
1229     {
1230         return path.contains (pluginClientModuleName
1231                              + File::getSeparatorString()
1232                              + subDir
1233                              + File::getSeparatorString());
1234     };
1235 
1236     auto isPluginClientSource = [&path, &pluginClientModuleName] (StringRef suffix)
1237     {
1238         auto prefix = pluginClientModuleName + "_" + suffix;
1239         return path.contains (prefix + ".") || path.contains (prefix + "_");
1240     };
1241 
1242     if (isPluginClientSource ("AU")         || isInPluginClientSubdir ("AU"))          return build_tools::ProjectType::Target::AudioUnitPlugIn;
1243     if (isPluginClientSource ("AUv3")       || isInPluginClientSubdir ("AU"))          return build_tools::ProjectType::Target::AudioUnitv3PlugIn;
1244     if (isPluginClientSource ("AAX")        || isInPluginClientSubdir ("AAX"))         return build_tools::ProjectType::Target::AAXPlugIn;
1245     if (isPluginClientSource ("RTAS")       || isInPluginClientSubdir ("RTAS"))        return build_tools::ProjectType::Target::RTASPlugIn;
1246     if (isPluginClientSource ("VST2")       || isInPluginClientSubdir ("VST"))         return build_tools::ProjectType::Target::VSTPlugIn;
1247     if (isPluginClientSource ("VST3")       || isInPluginClientSubdir ("VST3"))        return build_tools::ProjectType::Target::VST3PlugIn;
1248     if (isPluginClientSource ("Standalone") || isInPluginClientSubdir ("Standalone"))  return build_tools::ProjectType::Target::StandalonePlugIn;
1249     if (isPluginClientSource ("Unity")      || isInPluginClientSubdir ("Unity"))       return build_tools::ProjectType::Target::UnityPlugIn;
1250 
1251     return (returnSharedTargetIfNoValidSuffix ? build_tools::ProjectType::Target::SharedCodeTarget
1252                                               : build_tools::ProjectType::Target::unspecified);
1253 }
1254 
1255 //==============================================================================
createPropertyEditors(PropertyListBuilder & props)1256 void Project::createPropertyEditors (PropertyListBuilder& props)
1257 {
1258     props.add (new TextPropertyComponent (projectNameValue, "Project Name", 256, false),
1259                "The name of the project.");
1260 
1261     props.add (new TextPropertyComponent (versionValue, "Project Version", 16, false),
1262                "The project's version number. This should be in the format major.minor.point[.point] where you should omit the final "
1263                "(optional) [.point] if you are targeting AU and AUv3 plug-ins as they only support three number versions.");
1264 
1265     props.add (new ChoicePropertyComponent (projectLineFeedValue, "Project Line Feed", { "\\r\\n", "\\n", }, { "\r\n", "\n" }),
1266                "Use this to set the line feed which will be used when creating new source files for this project "
1267                "(this won't affect any existing files).");
1268 
1269     props.add (new TextPropertyComponent (companyNameValue, "Company Name", 256, false),
1270                "Your company name, which will be added to the properties of the binary where possible");
1271 
1272     props.add (new TextPropertyComponent (companyCopyrightValue, "Company Copyright", 256, false),
1273                "Your company copyright, which will be added to the properties of the binary where possible");
1274 
1275     props.add (new TextPropertyComponent (companyWebsiteValue, "Company Website", 256, false),
1276                "Your company website, which will be added to the properties of the binary where possible");
1277 
1278     props.add (new TextPropertyComponent (companyEmailValue, "Company E-mail", 256, false),
1279                "Your company e-mail, which will be added to the properties of the binary where possible");
1280 
1281     props.add (new ChoicePropertyComponent (useAppConfigValue, "Use Global AppConfig Header"),
1282                "If enabled, the Projucer will generate module wrapper stubs which include AppConfig.h "
1283                "and will include AppConfig.h in the JuceHeader.h. If disabled, all the settings that would "
1284                "previously have been specified in the AppConfig.h will be injected via the build system instead, "
1285                "which may simplify the includes in the project.");
1286 
1287     props.add (new ChoicePropertyComponent (addUsingNamespaceToJuceHeader, "Add \"using namespace juce\" to JuceHeader.h"),
1288                "If enabled, the JuceHeader.h will include a \"using namepace juce\" statement. If disabled, "
1289                "no such statement will be included. This setting used to be enabled by default, but it "
1290                "is recommended to leave it disabled for new projects.");
1291 
1292     props.add (new ChoicePropertyComponent (displaySplashScreenValue, "Display the JUCE Splash Screen (required for closed source applications without an Indie or Pro JUCE license)"),
1293                                             "This option controls the display of the standard JUCE splash screen. "
1294                                             "In accordance with the terms of the JUCE 6 End-Use License Agreement (www.juce.com/juce-6-licence), "
1295                                             "this option can only be disabled for closed source applications if you have a JUCE Indie or Pro "
1296                                             "license, or are using JUCE under the GPL v3 license.");
1297 
1298     props.add (new ChoicePropertyComponentWithEnablement (splashScreenColourValue, displaySplashScreenValue, "Splash Screen Colour",
1299                                                           { "Dark", "Light" }, { "Dark", "Light" }),
1300                "Choose the colour of the JUCE splash screen.");
1301 
1302 
1303     {
1304         StringArray projectTypeNames;
1305         Array<var> projectTypeCodes;
1306 
1307         auto types = build_tools::ProjectType::getAllTypes();
1308 
1309         for (int i = 0; i < types.size(); ++i)
1310         {
1311             projectTypeNames.add (types.getUnchecked(i)->getDescription());
1312             projectTypeCodes.add (types.getUnchecked(i)->getType());
1313         }
1314 
1315         props.add (new ChoicePropertyComponent (projectTypeValue, "Project Type", projectTypeNames, projectTypeCodes),
1316                    "The project type for which settings should be shown.");
1317     }
1318 
1319     props.add (new TextPropertyComponent (bundleIdentifierValue, "Bundle Identifier", 256, false),
1320                "A unique identifier for this product, mainly for use in OSX/iOS builds. It should be something like 'com.yourcompanyname.yourproductname'");
1321 
1322     if (isAudioPluginProject())
1323         createAudioPluginPropertyEditors (props);
1324 
1325     {
1326         const int maxSizes[] = { 20480, 10240, 6144, 2048, 1024, 512, 256, 128, 64 };
1327 
1328         StringArray maxSizeNames;
1329         Array<var> maxSizeCodes;
1330 
1331         for (int i = 0; i < numElementsInArray (maxSizes); ++i)
1332         {
1333             auto sizeInBytes = maxSizes[i] * 1024;
1334 
1335             maxSizeNames.add (File::descriptionOfSizeInBytes (sizeInBytes));
1336             maxSizeCodes.add (sizeInBytes);
1337         }
1338 
1339         props.add (new ChoicePropertyComponent (maxBinaryFileSizeValue, "BinaryData.cpp Size Limit", maxSizeNames, maxSizeCodes),
1340                    "When splitting binary data into multiple cpp files, the Projucer attempts to keep the file sizes below this threshold. "
1341                    "(Note that individual resource files which are larger than this size cannot be split across multiple cpp files).");
1342     }
1343 
1344     props.add (new ChoicePropertyComponent (includeBinaryDataInJuceHeaderValue, "Include BinaryData in JuceHeader"),
1345                                              "Include BinaryData.h in the JuceHeader.h file");
1346 
1347     props.add (new TextPropertyComponent (binaryDataNamespaceValue, "BinaryData Namespace", 256, false),
1348                                           "The namespace containing the binary assets.");
1349 
1350     props.add (new ChoicePropertyComponent (cppStandardValue, "C++ Language Standard",
1351                                             getCppStandardStrings(),
1352                                             getCppStandardVars()),
1353                "The standard of the C++ language that will be used for compilation.");
1354 
1355     props.add (new TextPropertyComponent (preprocessorDefsValue, "Preprocessor Definitions", 32768, true),
1356                "Global preprocessor definitions. Use the form \"NAME1=value NAME2=value\", using whitespace, commas, or "
1357                "new-lines to separate the items - to include a space or comma in a definition, precede it with a backslash.");
1358 
1359     props.addSearchPathProperty (headerSearchPathsValue, "Header Search Paths", "Global header search paths.");
1360 
1361     props.add (new TextPropertyComponent (postExportShellCommandPosixValue, "Post-Export Shell Command (macOS, Linux)", 1024, false),
1362                "A command that will be executed by the system shell after saving this project on macOS or Linux. "
1363                "The string \"%%1%%\" will be substituted with the absolute path to the project root folder.");
1364 
1365     props.add (new TextPropertyComponent (postExportShellCommandWinValue, "Post-Export Shell Command (Windows)", 1024, false),
1366                "A command that will be executed by the system shell after saving this project on Windows. "
1367                "The string \"%%1%%\" will be substituted with the absolute path to the project root folder.");
1368 
1369     props.add (new TextPropertyComponent (userNotesValue, "Notes", 32768, true),
1370                "Extra comments: This field is not used for code or project generation, it's just a space where you can express your thoughts.");
1371 }
1372 
createAudioPluginPropertyEditors(PropertyListBuilder & props)1373 void Project::createAudioPluginPropertyEditors (PropertyListBuilder& props)
1374 {
1375     props.add (new MultiChoicePropertyComponent (pluginFormatsValue, "Plugin Formats",
1376                                                  { "VST3", "AU", "AUv3", "RTAS (deprecated)", "AAX", "Standalone", "Unity", "Enable IAA", "VST (Legacy)" },
1377                                                  { Ids::buildVST3.toString(), Ids::buildAU.toString(), Ids::buildAUv3.toString(),
1378                                                    Ids::buildRTAS.toString(), Ids::buildAAX.toString(), Ids::buildStandalone.toString(), Ids::buildUnity.toString(),
1379                                                    Ids::enableIAA.toString(), Ids::buildVST.toString() }),
1380                "Plugin formats to build. If you have selected \"VST (Legacy)\" then you will need to ensure that you have a VST2 SDK "
1381                "in your header search paths. The VST2 SDK can be obtained from the vstsdk3610_11_06_2018_build_37 (or older) VST3 SDK "
1382                "or JUCE version 5.3.2. You also need a VST2 license from Steinberg to distribute VST2 plug-ins.");
1383     props.add (new MultiChoicePropertyComponent (pluginCharacteristicsValue, "Plugin Characteristics",
1384                                                  { "Plugin is a Synth", "Plugin MIDI Input", "Plugin MIDI Output", "MIDI Effect Plugin", "Plugin Editor Requires Keyboard Focus",
1385                                                    "Disable RTAS Bypass", "Disable AAX Bypass", "Disable RTAS Multi-Mono", "Disable AAX Multi-Mono" },
1386                                                  { Ids::pluginIsSynth.toString(), Ids::pluginWantsMidiIn.toString(), Ids::pluginProducesMidiOut.toString(),
1387                                                    Ids::pluginIsMidiEffectPlugin.toString(), Ids::pluginEditorRequiresKeys.toString(), Ids::pluginRTASDisableBypass.toString(),
1388                                                    Ids::pluginAAXDisableBypass.toString(), Ids::pluginRTASDisableMultiMono.toString(), Ids::pluginAAXDisableMultiMono.toString() }),
1389               "Some characteristics of your plugin such as whether it is a synth, produces MIDI messages, accepts MIDI messages etc.");
1390     props.add (new TextPropertyComponent (pluginNameValue, "Plugin Name", 128, false),
1391                "The name of your plugin (keep it short!)");
1392     props.add (new TextPropertyComponent (pluginDescriptionValue, "Plugin Description", 256, false),
1393                "A short description of your plugin.");
1394     props.add (new TextPropertyComponent (pluginManufacturerValue, "Plugin Manufacturer", 256, false),
1395                "The name of your company (cannot be blank).");
1396     props.add (new TextPropertyComponent (pluginManufacturerCodeValue, "Plugin Manufacturer Code", 4, false),
1397                "A four-character unique ID for your company. Note that for AU compatibility, this must contain at least one upper-case letter!"
1398                " GarageBand 10.3 requires the first letter to be upper-case, and the remaining letters to be lower-case.");
1399     props.add (new TextPropertyComponent (pluginCodeValue, "Plugin Code", 4, false),
1400                "A four-character unique ID for your plugin. Note that for AU compatibility, this must contain exactly one upper-case letter!"
1401                " GarageBand 10.3 requires the first letter to be upper-case, and the remaining letters to be lower-case.");
1402     props.add (new TextPropertyComponent (pluginChannelConfigsValue, "Plugin Channel Configurations", 1024, false),
1403                "This list is a comma-separated set list in the form {numIns, numOuts} and each pair indicates a valid plug-in "
1404                "configuration. For example {1, 1}, {2, 2} means that the plugin can be used either with 1 input and 1 output, "
1405                "or with 2 inputs and 2 outputs. If your plug-in requires side-chains, aux output buses etc., then you must leave "
1406                "this field empty and override the isBusesLayoutSupported callback in your AudioProcessor.");
1407     props.add (new TextPropertyComponent (pluginAAXIdentifierValue, "Plugin AAX Identifier", 256, false),
1408                "The value to use for the JucePlugin_AAXIdentifier setting");
1409     props.add (new TextPropertyComponent (pluginAUExportPrefixValue, "Plugin AU Export Prefix", 128, false),
1410                "A prefix for the names of exported entry-point functions that the component exposes - typically this will be a version of your plugin's name that can be used as part of a C++ token.");
1411     props.add (new MultiChoicePropertyComponent (pluginAUMainTypeValue, "Plugin AU Main Type", getAllAUMainTypeStrings(), getAllAUMainTypeVars(), 1),
1412                "AU main type.");
1413     props.add (new ChoicePropertyComponent (pluginAUSandboxSafeValue, "Plugin AU is sandbox safe"),
1414                "Check this box if your plug-in is sandbox safe. A sand-box safe plug-in is loaded in a restricted path and can only access it's own bundle resources and "
1415                "the Music folder. Your plug-in must be able to deal with this. Newer versions of GarageBand require this to be enabled.");
1416 
1417     {
1418         Array<var> varChoices;
1419         StringArray stringChoices;
1420 
1421         for (int i = 1; i <= 16; ++i)
1422         {
1423             varChoices.add (i);
1424             stringChoices.add (String (i));
1425         }
1426 
1427         props.add (new ChoicePropertyComponentWithEnablement (pluginVSTNumMidiInputsValue, pluginCharacteristicsValue, Ids::pluginWantsMidiIn,
1428                                                               "Plugin VST Num MIDI Inputs",  stringChoices, varChoices),
1429                    "For VST and VST3 plug-ins that accept MIDI, this allows you to configure the number of inputs.");
1430 
1431         props.add (new ChoicePropertyComponentWithEnablement (pluginVSTNumMidiOutputsValue, pluginCharacteristicsValue, Ids::pluginProducesMidiOut,
1432                                                               "Plugin VST Num MIDI Outputs", stringChoices, varChoices),
1433                    "For VST and VST3 plug-ins that produce MIDI, this allows you to configure the number of outputs.");
1434     }
1435 
1436     {
1437         Array<var> vst3CategoryVars;
1438 
1439         for (auto s : getAllVST3CategoryStrings())
1440             vst3CategoryVars.add (s);
1441 
1442         props.add (new MultiChoicePropertyComponent (pluginVST3CategoryValue, "Plugin VST3 Category", getAllVST3CategoryStrings(), vst3CategoryVars),
1443                    "VST3 category. Most hosts require either \"Fx\" or \"Instrument\" to be selected in order for the plugin to be recognised. "
1444                    "If neither of these are selected, the appropriate one will be automatically added based on the \"Plugin is a synth\" option.");
1445     }
1446 
1447     props.add (new MultiChoicePropertyComponent (pluginRTASCategoryValue, "Plugin RTAS Category", getAllRTASCategoryStrings(), getAllRTASCategoryVars()),
1448                "RTAS category.");
1449     props.add (new MultiChoicePropertyComponent (pluginAAXCategoryValue, "Plugin AAX Category", getAllAAXCategoryStrings(), getAllAAXCategoryVars()),
1450                "AAX category.");
1451 
1452     {
1453         Array<var> vstCategoryVars;
1454         for (auto s : getAllVSTCategoryStrings())
1455             vstCategoryVars.add (s);
1456 
1457         props.add (new MultiChoicePropertyComponent (pluginVSTCategoryValue, "Plugin VST (Legacy) Category", getAllVSTCategoryStrings(), vstCategoryVars, 1),
1458                    "VST category.");
1459     }
1460 }
1461 
1462 //==============================================================================
getBinaryDataCppFile(int index) const1463 File Project::getBinaryDataCppFile (int index) const
1464 {
1465     auto cpp = getGeneratedCodeFolder().getChildFile ("BinaryData.cpp");
1466 
1467     if (index > 0)
1468         return cpp.getSiblingFile (cpp.getFileNameWithoutExtension() + String (index + 1))
1469                     .withFileExtension (cpp.getFileExtension());
1470 
1471     return cpp;
1472 }
1473 
getMainGroup()1474 Project::Item Project::getMainGroup()
1475 {
1476     return { *this, projectRoot.getChildWithName (Ids::MAINGROUP), false };
1477 }
1478 
getStoredProperties() const1479 PropertiesFile& Project::getStoredProperties() const
1480 {
1481     return getAppSettings().getProjectProperties (getProjectUIDString());
1482 }
1483 
findImages(const Project::Item & item,OwnedArray<Project::Item> & found)1484 static void findImages (const Project::Item& item, OwnedArray<Project::Item>& found)
1485 {
1486     if (item.isImageFile())
1487     {
1488         found.add (new Project::Item (item));
1489     }
1490     else if (item.isGroup())
1491     {
1492         for (int i = 0; i < item.getNumChildren(); ++i)
1493             findImages (item.getChild (i), found);
1494     }
1495 }
1496 
findAllImageItems(OwnedArray<Project::Item> & items)1497 void Project::findAllImageItems (OwnedArray<Project::Item>& items)
1498 {
1499     findImages (getMainGroup(), items);
1500 }
1501 
1502 //==============================================================================
Item(Project & p,const ValueTree & s,bool isModuleCode)1503 Project::Item::Item (Project& p, const ValueTree& s, bool isModuleCode)
1504     : project (p), state (s), belongsToModule (isModuleCode)
1505 {
1506 }
1507 
Item(const Item & other)1508 Project::Item::Item (const Item& other)
1509     : project (other.project), state (other.state), belongsToModule (other.belongsToModule)
1510 {
1511 }
1512 
createCopy()1513 Project::Item Project::Item::createCopy()         { Item i (*this); i.state = i.state.createCopy(); return i; }
1514 
getID() const1515 String Project::Item::getID() const               { return state [Ids::ID]; }
setID(const String & newID)1516 void Project::Item::setID (const String& newID)   { state.setProperty (Ids::ID, newID, nullptr); }
1517 
loadAsImageFile() const1518 std::unique_ptr<Drawable> Project::Item::loadAsImageFile() const
1519 {
1520     const MessageManagerLock mml (ThreadPoolJob::getCurrentThreadPoolJob());
1521 
1522     if (! mml.lockWasGained())
1523         return nullptr;
1524 
1525     if (isValid())
1526         return Drawable::createFromImageFile (getFile());
1527 
1528     return {};
1529 }
1530 
createGroup(Project & project,const String & name,const String & uid,bool isModuleCode)1531 Project::Item Project::Item::createGroup (Project& project, const String& name, const String& uid, bool isModuleCode)
1532 {
1533     Item group (project, ValueTree (Ids::GROUP), isModuleCode);
1534     group.setID (uid);
1535     group.initialiseMissingProperties();
1536     group.getNameValue() = name;
1537     return group;
1538 }
1539 
isFile() const1540 bool Project::Item::isFile() const          { return state.hasType (Ids::FILE); }
isGroup() const1541 bool Project::Item::isGroup() const         { return state.hasType (Ids::GROUP) || isMainGroup(); }
isMainGroup() const1542 bool Project::Item::isMainGroup() const     { return state.hasType (Ids::MAINGROUP); }
1543 
isImageFile() const1544 bool Project::Item::isImageFile() const
1545 {
1546     return isFile() && (ImageFileFormat::findImageFormatForFileExtension (getFile()) != nullptr
1547                           || getFile().hasFileExtension ("svg"));
1548 }
1549 
isSourceFile() const1550 bool Project::Item::isSourceFile() const
1551 {
1552     return isFile() && getFile().hasFileExtension (sourceFileExtensions);
1553 }
1554 
findItemWithID(const String & targetId) const1555 Project::Item Project::Item::findItemWithID (const String& targetId) const
1556 {
1557     if (state [Ids::ID] == targetId)
1558         return *this;
1559 
1560     if (isGroup())
1561     {
1562         for (auto i = getNumChildren(); --i >= 0;)
1563         {
1564             auto found = getChild(i).findItemWithID (targetId);
1565 
1566             if (found.isValid())
1567                 return found;
1568         }
1569     }
1570 
1571     return Item (project, ValueTree(), false);
1572 }
1573 
canContain(const Item & child) const1574 bool Project::Item::canContain (const Item& child) const
1575 {
1576     if (isFile())
1577         return false;
1578 
1579     if (isGroup())
1580         return child.isFile() || child.isGroup();
1581 
1582     jassertfalse;
1583     return false;
1584 }
1585 
shouldBeAddedToTargetProject() const1586 bool Project::Item::shouldBeAddedToTargetProject() const    { return isFile(); }
1587 
shouldBeAddedToTargetExporter(const ProjectExporter & exporter) const1588 bool Project::Item::shouldBeAddedToTargetExporter (const ProjectExporter& exporter) const
1589 {
1590     if (shouldBeAddedToXcodeResources())
1591         return exporter.isXcode() || shouldBeCompiled();
1592 
1593     return true;
1594 }
1595 
getShouldCompileValue()1596 Value Project::Item::getShouldCompileValue()                { return state.getPropertyAsValue (Ids::compile, getUndoManager()); }
shouldBeCompiled() const1597 bool Project::Item::shouldBeCompiled() const                { return state [Ids::compile]; }
1598 
getShouldAddToBinaryResourcesValue()1599 Value Project::Item::getShouldAddToBinaryResourcesValue()   { return state.getPropertyAsValue (Ids::resource, getUndoManager()); }
shouldBeAddedToBinaryResources() const1600 bool Project::Item::shouldBeAddedToBinaryResources() const  { return state [Ids::resource]; }
1601 
getShouldAddToXcodeResourcesValue()1602 Value Project::Item::getShouldAddToXcodeResourcesValue()    { return state.getPropertyAsValue (Ids::xcodeResource, getUndoManager()); }
shouldBeAddedToXcodeResources() const1603 bool Project::Item::shouldBeAddedToXcodeResources() const   { return state [Ids::xcodeResource]; }
1604 
getShouldInhibitWarningsValue()1605 Value Project::Item::getShouldInhibitWarningsValue()        { return state.getPropertyAsValue (Ids::noWarnings, getUndoManager()); }
shouldInhibitWarnings() const1606 bool Project::Item::shouldInhibitWarnings() const           { return state [Ids::noWarnings]; }
1607 
isModuleCode() const1608 bool Project::Item::isModuleCode() const                    { return belongsToModule; }
1609 
getShouldSkipPCHValue()1610 Value Project::Item::getShouldSkipPCHValue()                { return state.getPropertyAsValue (Ids::skipPCH, getUndoManager()); }
shouldSkipPCH() const1611 bool Project::Item::shouldSkipPCH() const                   { return isModuleCode() || state [Ids::skipPCH]; }
1612 
getCompilerFlagSchemeValue()1613 Value Project::Item::getCompilerFlagSchemeValue()           { return state.getPropertyAsValue (Ids::compilerFlagScheme, getUndoManager()); }
getCompilerFlagSchemeString() const1614 String Project::Item::getCompilerFlagSchemeString() const   { return state [Ids::compilerFlagScheme]; }
1615 
setCompilerFlagScheme(const String & scheme)1616 void Project::Item::setCompilerFlagScheme (const String& scheme)
1617 {
1618     state.getPropertyAsValue (Ids::compilerFlagScheme, getUndoManager()).setValue (scheme);
1619 }
1620 
clearCurrentCompilerFlagScheme()1621 void Project::Item::clearCurrentCompilerFlagScheme()
1622 {
1623     state.removeProperty (Ids::compilerFlagScheme, getUndoManager());
1624 }
1625 
getFilePath() const1626 String Project::Item::getFilePath() const
1627 {
1628     if (isFile())
1629         return state [Ids::file].toString();
1630 
1631     return {};
1632 }
1633 
getFile() const1634 File Project::Item::getFile() const
1635 {
1636     if (isFile())
1637         return project.resolveFilename (state [Ids::file].toString());
1638 
1639     return {};
1640 }
1641 
setFile(const File & file)1642 void Project::Item::setFile (const File& file)
1643 {
1644     setFile (build_tools::RelativePath (project.getRelativePathForFile (file), build_tools::RelativePath::projectFolder));
1645     jassert (getFile() == file);
1646 }
1647 
setFile(const build_tools::RelativePath & file)1648 void Project::Item::setFile (const build_tools::RelativePath& file)
1649 {
1650     jassert (isFile());
1651     state.setProperty (Ids::file, file.toUnixStyle(), getUndoManager());
1652     state.setProperty (Ids::name, file.getFileName(), getUndoManager());
1653 }
1654 
renameFile(const File & newFile)1655 bool Project::Item::renameFile (const File& newFile)
1656 {
1657     auto oldFile = getFile();
1658 
1659     if (oldFile.moveFileTo (newFile)
1660          || (newFile.exists() && ! oldFile.exists()))
1661     {
1662         setFile (newFile);
1663         ProjucerApplication::getApp().openDocumentManager.fileHasBeenRenamed (oldFile, newFile);
1664         return true;
1665     }
1666 
1667     return false;
1668 }
1669 
containsChildForFile(const build_tools::RelativePath & file) const1670 bool Project::Item::containsChildForFile (const build_tools::RelativePath& file) const
1671 {
1672     return state.getChildWithProperty (Ids::file, file.toUnixStyle()).isValid();
1673 }
1674 
findItemForFile(const File & file) const1675 Project::Item Project::Item::findItemForFile (const File& file) const
1676 {
1677     if (getFile() == file)
1678         return *this;
1679 
1680     if (isGroup())
1681     {
1682         for (auto i = getNumChildren(); --i >= 0;)
1683         {
1684             auto found = getChild(i).findItemForFile (file);
1685 
1686             if (found.isValid())
1687                 return found;
1688         }
1689     }
1690 
1691     return Item (project, ValueTree(), false);
1692 }
1693 
determineGroupFolder() const1694 File Project::Item::determineGroupFolder() const
1695 {
1696     jassert (isGroup());
1697     File f;
1698 
1699     for (int i = 0; i < getNumChildren(); ++i)
1700     {
1701         f = getChild(i).getFile();
1702 
1703         if (f.exists())
1704             return f.getParentDirectory();
1705     }
1706 
1707     auto parent = getParent();
1708     if (parent != *this)
1709     {
1710         f = parent.determineGroupFolder();
1711 
1712         if (f.getChildFile (getName()).isDirectory())
1713             f = f.getChildFile (getName());
1714     }
1715     else
1716     {
1717         f = project.getProjectFolder();
1718 
1719         if (f.getChildFile ("Source").isDirectory())
1720             f = f.getChildFile ("Source");
1721     }
1722 
1723     return f;
1724 }
1725 
initialiseMissingProperties()1726 void Project::Item::initialiseMissingProperties()
1727 {
1728     if (! state.hasProperty (Ids::ID))
1729         setID (createAlphaNumericUID());
1730 
1731     if (isFile())
1732     {
1733         state.setProperty (Ids::name, getFile().getFileName(), nullptr);
1734     }
1735     else if (isGroup())
1736     {
1737         for (auto i = getNumChildren(); --i >= 0;)
1738             getChild(i).initialiseMissingProperties();
1739     }
1740 }
1741 
getNameValue()1742 Value Project::Item::getNameValue()
1743 {
1744     return state.getPropertyAsValue (Ids::name, getUndoManager());
1745 }
1746 
getName() const1747 String Project::Item::getName() const
1748 {
1749     return state [Ids::name];
1750 }
1751 
addChild(const Item & newChild,int insertIndex)1752 void Project::Item::addChild (const Item& newChild, int insertIndex)
1753 {
1754     state.addChild (newChild.state, insertIndex, getUndoManager());
1755 }
1756 
removeItemFromProject()1757 void Project::Item::removeItemFromProject()
1758 {
1759     state.getParent().removeChild (state, getUndoManager());
1760 }
1761 
getParent() const1762 Project::Item Project::Item::getParent() const
1763 {
1764     if (isMainGroup() || ! isGroup())
1765         return *this;
1766 
1767     return { project, state.getParent(), belongsToModule };
1768 }
1769 
1770 struct ItemSorter
1771 {
compareElementsItemSorter1772     static int compareElements (const ValueTree& first, const ValueTree& second)
1773     {
1774         return first [Ids::name].toString().compareNatural (second [Ids::name].toString());
1775     }
1776 };
1777 
1778 struct ItemSorterWithGroupsAtStart
1779 {
compareElementsItemSorterWithGroupsAtStart1780     static int compareElements (const ValueTree& first, const ValueTree& second)
1781     {
1782         auto firstIsGroup = first.hasType (Ids::GROUP);
1783         auto secondIsGroup = second.hasType (Ids::GROUP);
1784 
1785         if (firstIsGroup == secondIsGroup)
1786             return first [Ids::name].toString().compareNatural (second [Ids::name].toString());
1787 
1788         return firstIsGroup ? -1 : 1;
1789     }
1790 };
1791 
sortGroup(ValueTree & state,bool keepGroupsAtStart,UndoManager * undoManager)1792 static void sortGroup (ValueTree& state, bool keepGroupsAtStart, UndoManager* undoManager)
1793 {
1794     if (keepGroupsAtStart)
1795     {
1796         ItemSorterWithGroupsAtStart sorter;
1797         state.sort (sorter, undoManager, true);
1798     }
1799     else
1800     {
1801         ItemSorter sorter;
1802         state.sort (sorter, undoManager, true);
1803     }
1804 }
1805 
isGroupSorted(const ValueTree & state,bool keepGroupsAtStart)1806 static bool isGroupSorted (const ValueTree& state, bool keepGroupsAtStart)
1807 {
1808     if (state.getNumChildren() == 0)
1809         return false;
1810 
1811     if (state.getNumChildren() == 1)
1812         return true;
1813 
1814     auto stateCopy = state.createCopy();
1815     sortGroup (stateCopy, keepGroupsAtStart, nullptr);
1816     return stateCopy.isEquivalentTo (state);
1817 }
1818 
sortAlphabetically(bool keepGroupsAtStart,bool recursive)1819 void Project::Item::sortAlphabetically (bool keepGroupsAtStart, bool recursive)
1820 {
1821     sortGroup (state, keepGroupsAtStart, getUndoManager());
1822 
1823     if (recursive)
1824         for (auto i = getNumChildren(); --i >= 0;)
1825             getChild(i).sortAlphabetically (keepGroupsAtStart, true);
1826 }
1827 
getOrCreateSubGroup(const String & name)1828 Project::Item Project::Item::getOrCreateSubGroup (const String& name)
1829 {
1830     for (auto i = state.getNumChildren(); --i >= 0;)
1831     {
1832         auto child = state.getChild (i);
1833         if (child.getProperty (Ids::name) == name && child.hasType (Ids::GROUP))
1834             return { project, child, belongsToModule };
1835     }
1836 
1837     return addNewSubGroup (name, -1);
1838 }
1839 
addNewSubGroup(const String & name,int insertIndex)1840 Project::Item Project::Item::addNewSubGroup (const String& name, int insertIndex)
1841 {
1842     auto newID = createGUID (getID() + name + String (getNumChildren()));
1843 
1844     int n = 0;
1845     while (project.getMainGroup().findItemWithID (newID).isValid())
1846         newID = createGUID (newID + String (++n));
1847 
1848     auto group = createGroup (project, name, newID, belongsToModule);
1849 
1850     jassert (canContain (group));
1851     addChild (group, insertIndex);
1852     return group;
1853 }
1854 
addFileAtIndex(const File & file,int insertIndex,const bool shouldCompile)1855 bool Project::Item::addFileAtIndex (const File& file, int insertIndex, const bool shouldCompile)
1856 {
1857     if (file == File() || file.isHidden() || file.getFileName().startsWithChar ('.'))
1858         return false;
1859 
1860     if (file.isDirectory())
1861     {
1862         auto group = addNewSubGroup (file.getFileName(), insertIndex);
1863 
1864         for (const auto& iter : RangedDirectoryIterator (file, false, "*", File::findFilesAndDirectories))
1865             if (! project.getMainGroup().findItemForFile (iter.getFile()).isValid())
1866                 group.addFileRetainingSortOrder (iter.getFile(), shouldCompile);
1867     }
1868     else if (file.existsAsFile())
1869     {
1870         if (! project.getMainGroup().findItemForFile (file).isValid())
1871             addFileUnchecked (file, insertIndex, shouldCompile);
1872     }
1873     else
1874     {
1875         jassertfalse;
1876     }
1877 
1878     return true;
1879 }
1880 
addFileRetainingSortOrder(const File & file,bool shouldCompile)1881 bool Project::Item::addFileRetainingSortOrder (const File& file, bool shouldCompile)
1882 {
1883     auto wasSortedGroupsNotFirst = isGroupSorted (state, false);
1884     auto wasSortedGroupsFirst    = isGroupSorted (state, true);
1885 
1886     if (! addFileAtIndex (file, 0, shouldCompile))
1887         return false;
1888 
1889     if (wasSortedGroupsNotFirst || wasSortedGroupsFirst)
1890         sortAlphabetically (wasSortedGroupsFirst, false);
1891 
1892     return true;
1893 }
1894 
addFileUnchecked(const File & file,int insertIndex,const bool shouldCompile)1895 void Project::Item::addFileUnchecked (const File& file, int insertIndex, const bool shouldCompile)
1896 {
1897     Item item (project, ValueTree (Ids::FILE), belongsToModule);
1898     item.initialiseMissingProperties();
1899     item.getNameValue() = file.getFileName();
1900     item.getShouldCompileValue() = shouldCompile && file.hasFileExtension (fileTypesToCompileByDefault);
1901     item.getShouldAddToBinaryResourcesValue() = project.shouldBeAddedToBinaryResourcesByDefault (file);
1902 
1903     if (canContain (item))
1904     {
1905         item.setFile (file);
1906         addChild (item, insertIndex);
1907     }
1908 }
1909 
addRelativeFile(const build_tools::RelativePath & file,int insertIndex,bool shouldCompile)1910 bool Project::Item::addRelativeFile (const build_tools::RelativePath& file, int insertIndex, bool shouldCompile)
1911 {
1912     Item item (project, ValueTree (Ids::FILE), belongsToModule);
1913     item.initialiseMissingProperties();
1914     item.getNameValue() = file.getFileName();
1915     item.getShouldCompileValue() = shouldCompile;
1916     item.getShouldAddToBinaryResourcesValue() = project.shouldBeAddedToBinaryResourcesByDefault (file);
1917 
1918     if (canContain (item))
1919     {
1920         item.setFile (file);
1921         addChild (item, insertIndex);
1922         return true;
1923     }
1924 
1925     return false;
1926 }
1927 
getIcon(bool isOpen) const1928 Icon Project::Item::getIcon (bool isOpen) const
1929 {
1930     auto& icons = getIcons();
1931 
1932     if (isFile())
1933     {
1934         if (isImageFile())
1935             return Icon (icons.imageDoc, Colours::transparentBlack);
1936 
1937         return { icons.file, Colours::transparentBlack };
1938     }
1939 
1940     if (isMainGroup())
1941         return { icons.juceLogo, Colours::orange };
1942 
1943     return { isOpen ? icons.openFolder : icons.closedFolder, Colours::transparentBlack };
1944 }
1945 
isIconCrossedOut() const1946 bool Project::Item::isIconCrossedOut() const
1947 {
1948     return isFile()
1949             && ! (shouldBeCompiled()
1950                    || shouldBeAddedToBinaryResources()
1951                    || getFile().hasFileExtension (headerFileExtensions));
1952 }
1953 
needsSaving() const1954 bool Project::Item::needsSaving() const noexcept
1955 {
1956     auto& odm = ProjucerApplication::getApp().openDocumentManager;
1957 
1958     if (odm.anyFilesNeedSaving())
1959     {
1960         for (int i = 0; i < odm.getNumOpenDocuments(); ++i)
1961         {
1962             auto* doc = odm.getOpenDocument (i);
1963             if (doc->needsSaving() && doc->getFile() == getFile())
1964                 return true;
1965         }
1966     }
1967 
1968     return false;
1969 }
1970 
1971 //==============================================================================
getConfigNode()1972 ValueTree Project::getConfigNode()
1973 {
1974     return projectRoot.getOrCreateChildWithName (Ids::JUCEOPTIONS, nullptr);
1975 }
1976 
getConfigFlag(const String & name)1977 ValueWithDefault Project::getConfigFlag (const String& name)
1978 {
1979     auto configNode = getConfigNode();
1980 
1981     return { configNode, name, getUndoManagerFor (configNode) };
1982 }
1983 
isConfigFlagEnabled(const String & name,bool defaultIsEnabled) const1984 bool Project::isConfigFlagEnabled (const String& name, bool defaultIsEnabled) const
1985 {
1986     auto configValue = projectRoot.getChildWithName (Ids::JUCEOPTIONS).getProperty (name, "default");
1987 
1988     if (configValue == "default")
1989         return defaultIsEnabled;
1990 
1991     return configValue;
1992 }
1993 
1994 //==============================================================================
getCompilerFlagSchemes() const1995 StringArray Project::getCompilerFlagSchemes() const
1996 {
1997     if (compilerFlagSchemesValue.isUsingDefault())
1998         return {};
1999 
2000     StringArray schemes;
2001     auto schemesVar = compilerFlagSchemesValue.get();
2002 
2003     if (auto* arr = schemesVar.getArray())
2004         schemes.addArray (arr->begin(), arr->end());
2005 
2006     return schemes;
2007 }
2008 
addCompilerFlagScheme(const String & schemeToAdd)2009 void Project::addCompilerFlagScheme (const String& schemeToAdd)
2010 {
2011     auto schemesVar = compilerFlagSchemesValue.get();
2012 
2013     if (auto* arr = schemesVar.getArray())
2014     {
2015         arr->addIfNotAlreadyThere (schemeToAdd);
2016         compilerFlagSchemesValue.setValue ({ *arr }, getUndoManager());
2017     }
2018 }
2019 
removeCompilerFlagScheme(const String & schemeToRemove)2020 void Project::removeCompilerFlagScheme (const String& schemeToRemove)
2021 {
2022     auto schemesVar = compilerFlagSchemesValue.get();
2023 
2024     if (auto* arr = schemesVar.getArray())
2025     {
2026         for (int i = 0; i < arr->size(); ++i)
2027         {
2028             if (arr->getUnchecked (i).toString() == schemeToRemove)
2029             {
2030                 arr->remove (i);
2031 
2032                 if (arr->isEmpty())
2033                     compilerFlagSchemesValue.resetToDefault();
2034                 else
2035                     compilerFlagSchemesValue.setValue ({ *arr }, getUndoManager());
2036 
2037                 return;
2038             }
2039         }
2040     }
2041 }
2042 
2043 //==============================================================================
getCompanyNameOrDefault(StringRef str)2044 static String getCompanyNameOrDefault (StringRef str)
2045 {
2046     if (str.isEmpty())
2047         return "yourcompany";
2048 
2049     return str;
2050 }
2051 
getDefaultBundleIdentifierString() const2052 String Project::getDefaultBundleIdentifierString() const
2053 {
2054     return "com." + build_tools::makeValidIdentifier (getCompanyNameOrDefault (getCompanyNameString()), false, true, false)
2055             + "." + build_tools::makeValidIdentifier (getProjectNameString(), false, true, false);
2056 }
2057 
getDefaultPluginManufacturerString() const2058 String Project::getDefaultPluginManufacturerString() const
2059 {
2060     return getCompanyNameOrDefault (getCompanyNameString());
2061 }
2062 
getAUMainTypeString() const2063 String Project::getAUMainTypeString() const noexcept
2064 {
2065     auto v = pluginAUMainTypeValue.get();
2066 
2067     if (auto* arr = v.getArray())
2068         return arr->getFirst().toString();
2069 
2070     jassertfalse;
2071     return {};
2072 }
2073 
isAUSandBoxSafe() const2074 bool Project::isAUSandBoxSafe() const noexcept
2075 {
2076     return pluginAUSandboxSafeValue.get();
2077 }
2078 
getVSTCategoryString() const2079 String Project::getVSTCategoryString() const noexcept
2080 {
2081     auto v = pluginVSTCategoryValue.get();
2082 
2083     if (auto* arr = v.getArray())
2084         return arr->getFirst().toString();
2085 
2086     jassertfalse;
2087     return {};
2088 }
2089 
getVST3CategoryStringFromSelection(Array<var> selected,const Project & p)2090 static String getVST3CategoryStringFromSelection (Array<var> selected, const Project& p) noexcept
2091 {
2092     StringArray categories;
2093 
2094     for (auto& category : selected)
2095         categories.add (category);
2096 
2097     // One of these needs to be selected in order for the plug-in to be recognised in Cubase
2098     if (! categories.contains ("Fx") && ! categories.contains ("Instrument"))
2099     {
2100         categories.insert (0, p.isPluginSynth() ? "Instrument"
2101                                                 : "Fx");
2102     }
2103     else
2104     {
2105         // "Fx" and "Instrument" should come first and if both are present prioritise "Fx"
2106         if (categories.contains ("Instrument"))
2107             categories.move (categories.indexOf ("Instrument"), 0);
2108 
2109         if (categories.contains ("Fx"))
2110             categories.move (categories.indexOf ("Fx"), 0);
2111     }
2112 
2113     return categories.joinIntoString ("|");
2114 }
2115 
getVST3CategoryString() const2116 String Project::getVST3CategoryString() const noexcept
2117 {
2118     auto v = pluginVST3CategoryValue.get();
2119 
2120     if (auto* arr = v.getArray())
2121         return getVST3CategoryStringFromSelection (*arr, *this);
2122 
2123     jassertfalse;
2124     return {};
2125 }
2126 
getAAXCategory() const2127 int Project::getAAXCategory() const noexcept
2128 {
2129     int res = 0;
2130 
2131     auto v = pluginAAXCategoryValue.get();
2132 
2133     if (auto* arr = v.getArray())
2134     {
2135         for (auto c : *arr)
2136             res |= static_cast<int> (c);
2137     }
2138 
2139     return res;
2140 }
2141 
getRTASCategory() const2142 int Project::getRTASCategory() const noexcept
2143 {
2144     int res = 0;
2145 
2146     auto v = pluginRTASCategoryValue.get();
2147 
2148     if (auto* arr = v.getArray())
2149     {
2150         for (auto c : *arr)
2151             res |= static_cast<int> (c);
2152     }
2153 
2154     return res;
2155 }
2156 
getIAATypeCode() const2157 String Project::getIAATypeCode() const
2158 {
2159     String s;
2160     if (pluginWantsMidiInput())
2161     {
2162         if (isPluginSynth())
2163             s = "auri";
2164         else
2165             s = "aurm";
2166     }
2167     else
2168     {
2169         if (isPluginSynth())
2170             s = "aurg";
2171         else
2172             s = "aurx";
2173     }
2174     return s;
2175 }
2176 
getIAAPluginName() const2177 String Project::getIAAPluginName() const
2178 {
2179     auto s = getPluginManufacturerString();
2180     s << ": ";
2181     s << getPluginNameString();
2182     return s;
2183 }
2184 
2185 //==============================================================================
isAUPluginHost()2186 bool Project::isAUPluginHost()
2187 {
2188     return getEnabledModules().isModuleEnabled ("juce_audio_processors") && isConfigFlagEnabled ("JUCE_PLUGINHOST_AU", false);
2189 }
2190 
isVSTPluginHost()2191 bool Project::isVSTPluginHost()
2192 {
2193     return getEnabledModules().isModuleEnabled ("juce_audio_processors") && isConfigFlagEnabled ("JUCE_PLUGINHOST_VST", false);
2194 }
2195 
isVST3PluginHost()2196 bool Project::isVST3PluginHost()
2197 {
2198     return getEnabledModules().isModuleEnabled ("juce_audio_processors") && isConfigFlagEnabled ("JUCE_PLUGINHOST_VST3", false);
2199 }
2200 
2201 //==============================================================================
getAllAUMainTypeStrings()2202 StringArray Project::getAllAUMainTypeStrings() noexcept
2203 {
2204     static StringArray auMainTypeStrings { "kAudioUnitType_Effect", "kAudioUnitType_FormatConverter", "kAudioUnitType_Generator", "kAudioUnitType_MIDIProcessor",
2205                                            "kAudioUnitType_Mixer", "kAudioUnitType_MusicDevice", "kAudioUnitType_MusicEffect", "kAudioUnitType_OfflineEffect",
2206                                            "kAudioUnitType_Output", "kAudioUnitType_Panner" };
2207 
2208     return auMainTypeStrings;
2209 }
2210 
getAllAUMainTypeVars()2211 Array<var> Project::getAllAUMainTypeVars() noexcept
2212 {
2213     static Array<var> auMainTypeVars { "'aufx'", "'aufc'", "'augn'", "'aumi'",
2214                                        "'aumx'", "'aumu'", "'aumf'", "'auol'",
2215                                        "'auou'", "'aupn'" };
2216 
2217     return auMainTypeVars;
2218 }
2219 
getDefaultAUMainTypes() const2220 Array<var> Project::getDefaultAUMainTypes() const noexcept
2221 {
2222     if (isPluginMidiEffect())      return { "'aumi'" };
2223     if (isPluginSynth())           return { "'aumu'" };
2224     if (pluginWantsMidiInput())    return { "'aumf'" };
2225 
2226     return { "'aufx'" };
2227 }
2228 
getAllVSTCategoryStrings()2229 StringArray Project::getAllVSTCategoryStrings() noexcept
2230 {
2231     static StringArray vstCategoryStrings { "kPlugCategUnknown", "kPlugCategEffect", "kPlugCategSynth", "kPlugCategAnalysis", "kPlugCategMastering",
2232                                             "kPlugCategSpacializer", "kPlugCategRoomFx", "kPlugSurroundFx", "kPlugCategRestoration", "kPlugCategOfflineProcess",
2233                                             "kPlugCategShell", "kPlugCategGenerator" };
2234     return vstCategoryStrings;
2235 }
2236 
getDefaultVSTCategories() const2237 Array<var> Project::getDefaultVSTCategories() const noexcept
2238 {
2239     if (isPluginSynth())
2240         return  { "kPlugCategSynth" };
2241 
2242     return { "kPlugCategEffect" };
2243 }
2244 
getAllVST3CategoryStrings()2245 StringArray Project::getAllVST3CategoryStrings() noexcept
2246 {
2247     static StringArray vst3CategoryStrings { "Fx", "Instrument", "Analyzer", "Delay", "Distortion", "Drum", "Dynamics", "EQ", "External", "Filter",
2248                                              "Generator", "Mastering", "Modulation", "Mono", "Network", "NoOfflineProcess", "OnlyOfflineProcess", "OnlyRT",
2249                                              "Pitch Shift", "Restoration", "Reverb", "Sampler", "Spatial", "Stereo", "Surround", "Synth", "Tools", "Up-Downmix" };
2250 
2251     return vst3CategoryStrings;
2252 }
2253 
getDefaultVST3Categories() const2254 Array<var> Project::getDefaultVST3Categories() const noexcept
2255 {
2256     if (isPluginSynth())
2257         return  { "Instrument", "Synth" };
2258 
2259     return { "Fx" };
2260 }
2261 
getAllAAXCategoryStrings()2262 StringArray Project::getAllAAXCategoryStrings() noexcept
2263 {
2264     static StringArray aaxCategoryStrings { "AAX_ePlugInCategory_None", "AAX_ePlugInCategory_EQ", "AAX_ePlugInCategory_Dynamics", "AAX_ePlugInCategory_PitchShift",
2265                                             "AAX_ePlugInCategory_Reverb", "AAX_ePlugInCategory_Delay", "AAX_ePlugInCategory_Modulation", "AAX_ePlugInCategory_Harmonic",
2266                                             "AAX_ePlugInCategory_NoiseReduction", "AAX_ePlugInCategory_Dither", "AAX_ePlugInCategory_SoundField", "AAX_ePlugInCategory_HWGenerators",
2267                                             "AAX_ePlugInCategory_SWGenerators", "AAX_ePlugInCategory_WrappedPlugin", "AAX_EPlugInCategory_Effect" };
2268 
2269     return aaxCategoryStrings;
2270 }
2271 
getAllAAXCategoryVars()2272 Array<var> Project::getAllAAXCategoryVars() noexcept
2273 {
2274     static Array<var> aaxCategoryVars { 0x00000000, 0x00000001, 0x00000002, 0x00000004,
2275                                         0x00000008, 0x00000010, 0x00000020, 0x00000040,
2276                                         0x00000080, 0x00000100, 0x00000200, 0x00000400,
2277                                         0x00000800, 0x00001000, 0x00002000 };
2278 
2279     return aaxCategoryVars;
2280 }
2281 
getDefaultAAXCategories() const2282 Array<var> Project::getDefaultAAXCategories() const noexcept
2283 {
2284     if (isPluginSynth())
2285         return getAllAAXCategoryVars()[getAllAAXCategoryStrings().indexOf ("AAX_ePlugInCategory_SWGenerators")];
2286 
2287     return getAllAAXCategoryVars()[getAllAAXCategoryStrings().indexOf ("AAX_ePlugInCategory_None")];
2288 }
2289 
getAllRTASCategoryStrings()2290 StringArray Project::getAllRTASCategoryStrings() noexcept
2291 {
2292     static StringArray rtasCategoryStrings { "ePlugInCategory_None", "ePlugInCategory_EQ", "ePlugInCategory_Dynamics", "ePlugInCategory_PitchShift",
2293                                              "ePlugInCategory_Reverb", "ePlugInCategory_Delay", "ePlugInCategory_Modulation", "ePlugInCategory_Harmonic",
2294                                              "ePlugInCategory_NoiseReduction", "ePlugInCategory_Dither", "ePlugInCategory_SoundField", "ePlugInCategory_HWGenerators",
2295                                              "ePlugInCategory_SWGenerators", "ePlugInCategory_WrappedPlugin", "ePlugInCategory_Effect" };
2296 
2297     return rtasCategoryStrings;
2298 }
2299 
getAllRTASCategoryVars()2300 Array<var> Project::getAllRTASCategoryVars() noexcept
2301 {
2302     static Array<var> rtasCategoryVars { 0x00000000, 0x00000001, 0x00000002, 0x00000004,
2303                                          0x00000008, 0x00000010, 0x00000020, 0x00000040,
2304                                          0x00000080, 0x00000100, 0x00000200, 0x00000400,
2305                                          0x00000800, 0x00001000, 0x00002000 };
2306 
2307     return rtasCategoryVars;
2308 }
2309 
getDefaultRTASCategories() const2310 Array<var> Project::getDefaultRTASCategories() const noexcept
2311 {
2312     if (isPluginSynth())
2313         return getAllRTASCategoryVars()[getAllRTASCategoryStrings().indexOf ("ePlugInCategory_SWGenerators")];
2314 
2315     return getAllRTASCategoryVars()[getAllRTASCategoryStrings().indexOf ("ePlugInCategory_None")];
2316 }
2317 
2318 //==============================================================================
getEnabledModules()2319 EnabledModulesList& Project::getEnabledModules()
2320 {
2321     if (enabledModulesList == nullptr)
2322         enabledModulesList.reset (new EnabledModulesList (*this, projectRoot.getOrCreateChildWithName (Ids::MODULES, nullptr)));
2323 
2324     return *enabledModulesList;
2325 }
2326 
getModulePathsFromExporters(Project & project,bool onlyThisOS)2327 static StringArray getModulePathsFromExporters (Project& project, bool onlyThisOS)
2328 {
2329     StringArray paths;
2330 
2331     for (Project::ExporterIterator exporter (project); exporter.next();)
2332     {
2333         if (onlyThisOS && ! exporter->mayCompileOnCurrentOS())
2334             continue;
2335 
2336         auto& modules = project.getEnabledModules();
2337         auto n = modules.getNumModules();
2338 
2339         for (int i = 0; i < n; ++i)
2340         {
2341             auto id = modules.getModuleID (i);
2342 
2343             if (modules.shouldUseGlobalPath (id))
2344                 continue;
2345 
2346             auto path = exporter->getPathForModuleString (id);
2347 
2348             if (path.isNotEmpty())
2349                 paths.addIfNotAlreadyThere (path);
2350         }
2351 
2352         auto oldPath = exporter->getLegacyModulePath();
2353 
2354         if (oldPath.isNotEmpty())
2355             paths.addIfNotAlreadyThere (oldPath);
2356     }
2357 
2358     return paths;
2359 }
2360 
getExporterModulePathsToScan(Project & project)2361 static Array<File> getExporterModulePathsToScan (Project& project)
2362 {
2363     auto exporterPaths = getModulePathsFromExporters (project, true);
2364 
2365     if (exporterPaths.isEmpty())
2366         exporterPaths = getModulePathsFromExporters (project, false);
2367 
2368     Array<File> files;
2369 
2370     for (auto& path : exporterPaths)
2371     {
2372         auto f = project.resolveFilename (path);
2373 
2374         if (f.isDirectory())
2375         {
2376             files.addIfNotAlreadyThere (f);
2377 
2378             if (f.getChildFile ("modules").isDirectory())
2379                 files.addIfNotAlreadyThere (f.getChildFile ("modules"));
2380         }
2381     }
2382 
2383     return files;
2384 }
2385 
rescanExporterPathModules(bool async)2386 void Project::rescanExporterPathModules (bool async)
2387 {
2388     if (async)
2389         exporterPathsModulesList.scanPathsAsync (getExporterModulePathsToScan (*this));
2390     else
2391         exporterPathsModulesList.scanPaths (getExporterModulePathsToScan (*this));
2392 }
2393 
getModuleWithID(const String & id)2394 AvailableModulesList::ModuleIDAndFolder Project::getModuleWithID (const String& id)
2395 {
2396     if (! getEnabledModules().shouldUseGlobalPath (id))
2397     {
2398         const auto& mod = exporterPathsModulesList.getModuleWithID (id);
2399 
2400         if (mod.second != File())
2401             return mod;
2402     }
2403 
2404     const auto& list = (isJUCEModule (id) ? ProjucerApplication::getApp().getJUCEPathModulesList().getAllModules()
2405                                           : ProjucerApplication::getApp().getUserPathsModulesList().getAllModules());
2406 
2407     for (auto& m : list)
2408         if (m.first == id)
2409             return m;
2410 
2411     return exporterPathsModulesList.getModuleWithID (id);
2412 }
2413 
2414 //==============================================================================
getExporters()2415 ValueTree Project::getExporters()
2416 {
2417     return projectRoot.getOrCreateChildWithName (Ids::EXPORTFORMATS, nullptr);
2418 }
2419 
getNumExporters()2420 int Project::getNumExporters()
2421 {
2422     return getExporters().getNumChildren();
2423 }
2424 
createExporter(int index)2425 std::unique_ptr<ProjectExporter> Project::createExporter (int index)
2426 {
2427     jassert (index >= 0 && index < getNumExporters());
2428     return ProjectExporter::createExporterFromSettings (*this, getExporters().getChild (index));
2429 }
2430 
addNewExporter(const Identifier & exporterIdentifier)2431 void Project::addNewExporter (const Identifier& exporterIdentifier)
2432 {
2433     std::unique_ptr<ProjectExporter> exp (ProjectExporter::createNewExporter (*this, exporterIdentifier));
2434 
2435     exp->getTargetLocationValue() = exp->getTargetLocationString()
2436                                        + getUniqueTargetFolderSuffixForExporter (exporterIdentifier, exp->getTargetLocationString());
2437 
2438     auto exportersTree = getExporters();
2439     exportersTree.appendChild (exp->settings, getUndoManagerFor (exportersTree));
2440 }
2441 
createExporterForCurrentPlatform()2442 void Project::createExporterForCurrentPlatform()
2443 {
2444     addNewExporter (ProjectExporter::getCurrentPlatformExporterTypeInfo().identifier);
2445 }
2446 
getUniqueTargetFolderSuffixForExporter(const Identifier & exporterIdentifier,const String & base)2447 String Project::getUniqueTargetFolderSuffixForExporter (const Identifier& exporterIdentifier, const String& base)
2448 {
2449     StringArray buildFolders;
2450 
2451     auto exportersTree = getExporters();
2452 
2453     for (int i = 0; i < exportersTree.getNumChildren(); ++i)
2454     {
2455         auto exporterNode = exportersTree.getChild (i);
2456 
2457         if (exporterNode.getType() == exporterIdentifier)
2458             buildFolders.add (exporterNode.getProperty ("targetFolder").toString());
2459     }
2460 
2461     if (buildFolders.size() == 0 || ! buildFolders.contains (base))
2462         return {};
2463 
2464     buildFolders.remove (buildFolders.indexOf (base));
2465 
2466     int num = 1;
2467     for (auto f : buildFolders)
2468     {
2469         if (! f.endsWith ("_" + String (num)))
2470             break;
2471 
2472         ++num;
2473     }
2474 
2475     return "_" + String (num);
2476 }
2477 
2478 //==============================================================================
getAppConfigDefs()2479 StringPairArray Project::getAppConfigDefs()
2480 {
2481     StringPairArray result;
2482     result.set ("JUCE_DISPLAY_SPLASH_SCREEN",  shouldDisplaySplashScreen()             ? "1" : "0");
2483     result.set ("JUCE_USE_DARK_SPLASH_SCREEN", getSplashScreenColourString() == "Dark" ? "1" : "0");
2484     result.set ("JUCE_PROJUCER_VERSION",       "0x" + String::toHexString (ProjectInfo::versionNumber));
2485 
2486     OwnedArray<LibraryModule> modules;
2487     getEnabledModules().createRequiredModules (modules);
2488 
2489     for (auto& m : modules)
2490         result.set ("JUCE_MODULE_AVAILABLE_" + m->getID(), "1");
2491 
2492     result.set ("JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED", "1");
2493 
2494     for (auto& m : modules)
2495     {
2496         OwnedArray<Project::ConfigFlag> flags;
2497         m->getConfigFlags (*this, flags);
2498 
2499         for (auto* flag : flags)
2500             if (! flag->value.isUsingDefault())
2501                 result.set (flag->symbol, flag->value.get() ? "1" : "0");
2502     }
2503 
2504     result.addArray (getAudioPluginFlags());
2505 
2506     const auto& type = getProjectType();
2507     const auto isStandaloneApplication = (! type.isAudioPlugin() && ! type.isDynamicLibrary());
2508 
2509     const auto standaloneValue = [&]
2510     {
2511         if (result.containsKey ("JucePlugin_Name") && result.containsKey ("JucePlugin_Build_Standalone"))
2512             return "JucePlugin_Build_Standalone";
2513 
2514         return isStandaloneApplication ? "1" : "0";
2515     }();
2516 
2517     result.set ("JUCE_STANDALONE_APPLICATION", standaloneValue);
2518 
2519     return result;
2520 }
2521 
getAudioPluginFlags() const2522 StringPairArray Project::getAudioPluginFlags() const
2523 {
2524     if (! isAudioPluginProject())
2525         return {};
2526 
2527     const auto boolToString = [] (bool b) { return b ? "1" : "0"; };
2528 
2529     const auto toStringLiteral = [] (const String& v)
2530     {
2531         return CppTokeniserFunctions::addEscapeChars (v).quoted();
2532     };
2533 
2534     const auto countMaxPluginChannels = [] (const String& configString, bool isInput)
2535     {
2536         auto configs = StringArray::fromTokens (configString, ", {}", {});
2537         configs.trim();
2538         configs.removeEmptyStrings();
2539         jassert ((configs.size() & 1) == 0);  // looks like a syntax error in the configs?
2540 
2541         int maxVal = 0;
2542 
2543         for (int i = (isInput ? 0 : 1); i < configs.size(); i += 2)
2544             maxVal = jmax (maxVal, configs[i].getIntValue());
2545 
2546         return maxVal;
2547     };
2548 
2549     const auto toCharLiteral = [] (const String& v)
2550     {
2551         auto fourCharCode = v.substring (0, 4);
2552         uint32 hexRepresentation = 0;
2553 
2554         for (int i = 0; i < 4; ++i)
2555             hexRepresentation = (hexRepresentation << 8u)
2556                                 |  (static_cast<unsigned int> (fourCharCode[i]) & 0xffu);
2557 
2558         return "0x" + String::toHexString (static_cast<int> (hexRepresentation));
2559     };
2560 
2561     StringPairArray flags;
2562     flags.set ("JucePlugin_Build_VST",                   boolToString (shouldBuildVST()));
2563     flags.set ("JucePlugin_Build_VST3",                  boolToString (shouldBuildVST3()));
2564     flags.set ("JucePlugin_Build_AU",                    boolToString (shouldBuildAU()));
2565     flags.set ("JucePlugin_Build_AUv3",                  boolToString (shouldBuildAUv3()));
2566     flags.set ("JucePlugin_Build_RTAS",                  boolToString (shouldBuildRTAS()));
2567     flags.set ("JucePlugin_Build_AAX",                   boolToString (shouldBuildAAX()));
2568     flags.set ("JucePlugin_Build_Standalone",            boolToString (shouldBuildStandalonePlugin()));
2569     flags.set ("JucePlugin_Build_Unity",                 boolToString (shouldBuildUnityPlugin()));
2570     flags.set ("JucePlugin_Enable_IAA",                  boolToString (shouldEnableIAA()));
2571     flags.set ("JucePlugin_Name",                        toStringLiteral (getPluginNameString()));
2572     flags.set ("JucePlugin_Desc",                        toStringLiteral (getPluginDescriptionString()));
2573     flags.set ("JucePlugin_Manufacturer",                toStringLiteral (getPluginManufacturerString()));
2574     flags.set ("JucePlugin_ManufacturerWebsite",         toStringLiteral (getCompanyWebsiteString()));
2575     flags.set ("JucePlugin_ManufacturerEmail",           toStringLiteral (getCompanyEmailString()));
2576     flags.set ("JucePlugin_ManufacturerCode",            toCharLiteral (getPluginManufacturerCodeString()));
2577     flags.set ("JucePlugin_PluginCode",                  toCharLiteral (getPluginCodeString()));
2578     flags.set ("JucePlugin_IsSynth",                     boolToString (isPluginSynth()));
2579     flags.set ("JucePlugin_WantsMidiInput",              boolToString (pluginWantsMidiInput()));
2580     flags.set ("JucePlugin_ProducesMidiOutput",          boolToString (pluginProducesMidiOutput()));
2581     flags.set ("JucePlugin_IsMidiEffect",                boolToString (isPluginMidiEffect()));
2582     flags.set ("JucePlugin_EditorRequiresKeyboardFocus", boolToString (pluginEditorNeedsKeyFocus()));
2583     flags.set ("JucePlugin_Version",                     getVersionString());
2584     flags.set ("JucePlugin_VersionCode",                 getVersionAsHex());
2585     flags.set ("JucePlugin_VersionString",               toStringLiteral (getVersionString()));
2586     flags.set ("JucePlugin_VSTUniqueID",                 "JucePlugin_PluginCode");
2587     flags.set ("JucePlugin_VSTCategory",                 getVSTCategoryString());
2588     flags.set ("JucePlugin_Vst3Category",                toStringLiteral (getVST3CategoryString()));
2589     flags.set ("JucePlugin_AUMainType",                  getAUMainTypeString());
2590     flags.set ("JucePlugin_AUSubType",                   "JucePlugin_PluginCode");
2591     flags.set ("JucePlugin_AUExportPrefix",              getPluginAUExportPrefixString());
2592     flags.set ("JucePlugin_AUExportPrefixQuoted",        toStringLiteral (getPluginAUExportPrefixString()));
2593     flags.set ("JucePlugin_AUManufacturerCode",          "JucePlugin_ManufacturerCode");
2594     flags.set ("JucePlugin_CFBundleIdentifier",          getBundleIdentifierString());
2595     flags.set ("JucePlugin_RTASCategory",                String (getRTASCategory()));
2596     flags.set ("JucePlugin_RTASManufacturerCode",        "JucePlugin_ManufacturerCode");
2597     flags.set ("JucePlugin_RTASProductId",               "JucePlugin_PluginCode");
2598     flags.set ("JucePlugin_RTASDisableBypass",           boolToString (isPluginRTASBypassDisabled()));
2599     flags.set ("JucePlugin_RTASDisableMultiMono",        boolToString (isPluginRTASMultiMonoDisabled()));
2600     flags.set ("JucePlugin_AAXIdentifier",               getAAXIdentifierString());
2601     flags.set ("JucePlugin_AAXManufacturerCode",         "JucePlugin_ManufacturerCode");
2602     flags.set ("JucePlugin_AAXProductId",                "JucePlugin_PluginCode");
2603     flags.set ("JucePlugin_AAXCategory",                 String (getAAXCategory()));
2604     flags.set ("JucePlugin_AAXDisableBypass",            boolToString (isPluginAAXBypassDisabled()));
2605     flags.set ("JucePlugin_AAXDisableMultiMono",         boolToString (isPluginAAXMultiMonoDisabled()));
2606     flags.set ("JucePlugin_IAAType",                     toCharLiteral (getIAATypeCode()));
2607     flags.set ("JucePlugin_IAASubType",                  "JucePlugin_PluginCode");
2608     flags.set ("JucePlugin_IAAName",                     getIAAPluginName().quoted());
2609     flags.set ("JucePlugin_VSTNumMidiInputs",            getVSTNumMIDIInputsString());
2610     flags.set ("JucePlugin_VSTNumMidiOutputs",           getVSTNumMIDIOutputsString());
2611 
2612     {
2613         String plugInChannelConfig = getPluginChannelConfigsString();
2614 
2615         if (plugInChannelConfig.isNotEmpty())
2616         {
2617             flags.set ("JucePlugin_MaxNumInputChannels",            String (countMaxPluginChannels (plugInChannelConfig, true)));
2618             flags.set ("JucePlugin_MaxNumOutputChannels",           String (countMaxPluginChannels (plugInChannelConfig, false)));
2619             flags.set ("JucePlugin_PreferredChannelConfigurations", plugInChannelConfig);
2620         }
2621     }
2622 
2623     return flags;
2624 }
2625 
2626 //==============================================================================
ExporterIterator(Project & p)2627 Project::ExporterIterator::ExporterIterator (Project& p) : index (-1), project (p) {}
~ExporterIterator()2628 Project::ExporterIterator::~ExporterIterator() {}
2629 
next()2630 bool Project::ExporterIterator::next()
2631 {
2632     if (++index >= project.getNumExporters())
2633         return false;
2634 
2635     exporter = project.createExporter (index);
2636 
2637     if (exporter == nullptr)
2638     {
2639         jassertfalse; // corrupted project file?
2640         return next();
2641     }
2642 
2643     return true;
2644 }
2645