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