The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2231 lines
82KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE 6 technical preview.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. You may use this code under the terms of the GPL v3
  6. (see www.gnu.org/licenses).
  7. For this technical preview, this file is not subject to commercial licensing.
  8. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  9. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  10. DISCLAIMED.
  11. ==============================================================================
  12. */
  13. #include "../Application/jucer_Headers.h"
  14. #include "jucer_Project.h"
  15. #include "../ProjectSaving/jucer_ProjectSaver.h"
  16. #include "../Application/jucer_Application.h"
  17. #include "../LiveBuildEngine/jucer_CompileEngineSettings.h"
  18. namespace
  19. {
  20. String makeValid4CC (const String& seed)
  21. {
  22. auto s = CodeHelpers::makeValidIdentifier (seed, false, true, false) + "xxxx";
  23. return s.substring (0, 1).toUpperCase()
  24. + s.substring (1, 4).toLowerCase();
  25. }
  26. }
  27. //==============================================================================
  28. Project::Project (const File& f)
  29. : FileBasedDocument (projectFileExtension,
  30. String ("*") + projectFileExtension,
  31. "Choose a Jucer project to load",
  32. "Save Jucer project")
  33. {
  34. Logger::writeToLog ("Loading project: " + f.getFullPathName());
  35. setFile (f);
  36. removeDefunctExporters();
  37. exporterPathsModuleList.reset (new AvailableModuleList());
  38. updateOldModulePaths();
  39. updateOldStyleConfigList();
  40. setCppVersionFromOldExporterSettings();
  41. moveOldPropertyFromProjectToAllExporters (Ids::bigIcon);
  42. moveOldPropertyFromProjectToAllExporters (Ids::smallIcon);
  43. initialiseProjectValues();
  44. initialiseMainGroup();
  45. initialiseAudioPluginValues();
  46. parsedPreprocessorDefs = parsePreprocessorDefs (preprocessorDefsValue.get());
  47. getEnabledModules().sortAlphabetically();
  48. projectRoot.addListener (this);
  49. compileEngineSettings.reset (new CompileEngineSettings (projectRoot));
  50. setChangedFlag (false);
  51. modificationTime = getFile().getLastModificationTime();
  52. }
  53. Project::~Project()
  54. {
  55. projectRoot.removeListener (this);
  56. ProjucerApplication::getApp().openDocumentManager.closeAllDocumentsUsingProject (*this, false);
  57. }
  58. const char* Project::projectFileExtension = ".jucer";
  59. //==============================================================================
  60. void Project::setTitle (const String& newTitle)
  61. {
  62. projectNameValue = newTitle;
  63. updateTitleDependencies();
  64. }
  65. void Project::updateTitleDependencies()
  66. {
  67. auto projectName = getProjectNameString();
  68. getMainGroup().getNameValue() = projectName;
  69. pluginNameValue. setDefault (projectName);
  70. pluginDescriptionValue. setDefault (projectName);
  71. bundleIdentifierValue. setDefault (getDefaultBundleIdentifierString());
  72. pluginAUExportPrefixValue.setDefault (CodeHelpers::makeValidIdentifier (projectName, false, true, false) + "AU");
  73. pluginAAXIdentifierValue. setDefault (getDefaultAAXIdentifierString());
  74. }
  75. String Project::getDocumentTitle()
  76. {
  77. return getProjectNameString();
  78. }
  79. void Project::updateCompanyNameDependencies()
  80. {
  81. bundleIdentifierValue.setDefault (getDefaultBundleIdentifierString());
  82. pluginAAXIdentifierValue.setDefault (getDefaultAAXIdentifierString());
  83. pluginManufacturerValue.setDefault (getDefaultPluginManufacturerString());
  84. }
  85. void Project::updateProjectSettings()
  86. {
  87. projectRoot.setProperty (Ids::jucerVersion, ProjectInfo::versionString, nullptr);
  88. projectRoot.setProperty (Ids::name, getDocumentTitle(), nullptr);
  89. }
  90. bool Project::setCppVersionFromOldExporterSettings()
  91. {
  92. auto highestLanguageStandard = -1;
  93. for (Project::ExporterIterator exporter (*this); exporter.next();)
  94. {
  95. if (exporter->isXcode()) // cpp version was per-build configuration for xcode exporters
  96. {
  97. for (ProjectExporter::ConfigIterator config (*exporter); config.next();)
  98. {
  99. auto cppLanguageStandard = config->getValue (Ids::cppLanguageStandard).getValue();
  100. if (cppLanguageStandard != var())
  101. {
  102. auto versionNum = cppLanguageStandard.toString().getLastCharacters (2).getIntValue();
  103. if (versionNum > highestLanguageStandard)
  104. highestLanguageStandard = versionNum;
  105. }
  106. }
  107. }
  108. else
  109. {
  110. auto cppLanguageStandard = exporter->getSetting (Ids::cppLanguageStandard).getValue();
  111. if (cppLanguageStandard != var())
  112. {
  113. if (cppLanguageStandard.toString().containsIgnoreCase ("latest"))
  114. {
  115. cppStandardValue = "latest";
  116. return true;
  117. }
  118. auto versionNum = cppLanguageStandard.toString().getLastCharacters (2).getIntValue();
  119. if (versionNum > highestLanguageStandard)
  120. highestLanguageStandard = versionNum;
  121. }
  122. }
  123. }
  124. if (highestLanguageStandard != -1 && highestLanguageStandard >= 11)
  125. {
  126. cppStandardValue = highestLanguageStandard;
  127. return true;
  128. }
  129. return false;
  130. }
  131. void Project::updateDeprecatedProjectSettingsInteractively()
  132. {
  133. jassert (! ProjucerApplication::getApp().isRunningCommandLine);
  134. for (Project::ExporterIterator exporter (*this); exporter.next();)
  135. exporter->updateDeprecatedProjectSettingsInteractively();
  136. }
  137. void Project::initialiseMainGroup()
  138. {
  139. // Create main file group if missing
  140. if (! projectRoot.getChildWithName (Ids::MAINGROUP).isValid())
  141. {
  142. Item mainGroup (*this, ValueTree (Ids::MAINGROUP), false);
  143. projectRoot.addChild (mainGroup.state, 0, nullptr);
  144. }
  145. getMainGroup().initialiseMissingProperties();
  146. }
  147. void Project::initialiseProjectValues()
  148. {
  149. projectNameValue.referTo (projectRoot, Ids::name, getUndoManager(), "JUCE Project");
  150. projectUIDValue.referTo (projectRoot, Ids::ID, getUndoManager(), createAlphaNumericUID());
  151. if (projectUIDValue.isUsingDefault())
  152. projectUIDValue = projectUIDValue.getDefault();
  153. projectLineFeedValue.referTo (projectRoot, Ids::projectLineFeed, getUndoManager(), "\r\n");
  154. companyNameValue.referTo (projectRoot, Ids::companyName, getUndoManager());
  155. companyCopyrightValue.referTo (projectRoot, Ids::companyCopyright, getUndoManager());
  156. companyWebsiteValue.referTo (projectRoot, Ids::companyWebsite, getUndoManager());
  157. companyEmailValue.referTo (projectRoot, Ids::companyEmail, getUndoManager());
  158. projectTypeValue.referTo (projectRoot, Ids::projectType, getUndoManager(), ProjectType_GUIApp::getTypeName());
  159. versionValue.referTo (projectRoot, Ids::version, getUndoManager(), "1.0.0");
  160. bundleIdentifierValue.referTo (projectRoot, Ids::bundleIdentifier, getUndoManager(), getDefaultBundleIdentifierString());
  161. displaySplashScreenValue.referTo (projectRoot, Ids::displaySplashScreen, getUndoManager(), ! ProjucerApplication::getApp().isPaidOrGPL());
  162. splashScreenColourValue.referTo (projectRoot, Ids::splashScreenColour, getUndoManager(), "Dark");
  163. reportAppUsageValue.referTo (projectRoot, Ids::reportAppUsage, getUndoManager());
  164. if (ProjucerApplication::getApp().isPaidOrGPL())
  165. {
  166. reportAppUsageValue.setDefault (ProjucerApplication::getApp().licenseController->getState().applicationUsageDataState
  167. == LicenseState::ApplicationUsageData::enabled);
  168. }
  169. else
  170. {
  171. reportAppUsageValue.setDefault (true);
  172. }
  173. cppStandardValue.referTo (projectRoot, Ids::cppLanguageStandard, getUndoManager(), "14");
  174. headerSearchPathsValue.referTo (projectRoot, Ids::headerPath, getUndoManager());
  175. preprocessorDefsValue.referTo (projectRoot, Ids::defines, getUndoManager());
  176. userNotesValue.referTo (projectRoot, Ids::userNotes, getUndoManager());
  177. maxBinaryFileSizeValue.referTo (projectRoot, Ids::maxBinaryFileSize, getUndoManager(), 10240 * 1024);
  178. // this is here for backwards compatibility with old projects using the incorrect id
  179. if (projectRoot.hasProperty ("includeBinaryInAppConfig"))
  180. includeBinaryDataInJuceHeaderValue.referTo (projectRoot, "includeBinaryInAppConfig", getUndoManager(), true);
  181. else
  182. includeBinaryDataInJuceHeaderValue.referTo (projectRoot, Ids::includeBinaryInJuceHeader, getUndoManager(), true);
  183. binaryDataNamespaceValue.referTo (projectRoot, Ids::binaryDataNamespace, getUndoManager(), "BinaryData");
  184. compilerFlagSchemesValue.referTo (projectRoot, Ids::compilerFlagSchemes, getUndoManager(), Array<var>(), ",");
  185. postExportShellCommandPosixValue.referTo (projectRoot, Ids::postExportShellCommandPosix, getUndoManager());
  186. postExportShellCommandWinValue.referTo (projectRoot, Ids::postExportShellCommandWin, getUndoManager());
  187. }
  188. void Project::initialiseAudioPluginValues()
  189. {
  190. pluginFormatsValue.referTo (projectRoot, Ids::pluginFormats, getUndoManager(),
  191. Array<var> (Ids::buildVST3.toString(), Ids::buildAU.toString(), Ids::buildStandalone.toString()), ",");
  192. pluginCharacteristicsValue.referTo (projectRoot, Ids::pluginCharacteristicsValue, getUndoManager(), Array<var> (), ",");
  193. pluginNameValue.referTo (projectRoot, Ids::pluginName, getUndoManager(), getProjectNameString());
  194. pluginDescriptionValue.referTo (projectRoot, Ids::pluginDesc, getUndoManager(), getProjectNameString());
  195. pluginManufacturerValue.referTo (projectRoot, Ids::pluginManufacturer, getUndoManager(), getDefaultPluginManufacturerString());
  196. pluginManufacturerCodeValue.referTo (projectRoot, Ids::pluginManufacturerCode, getUndoManager(), "Manu");
  197. pluginCodeValue.referTo (projectRoot, Ids::pluginCode, getUndoManager(), makeValid4CC (getProjectUIDString() + getProjectUIDString()));
  198. pluginChannelConfigsValue.referTo (projectRoot, Ids::pluginChannelConfigs, getUndoManager());
  199. pluginAAXIdentifierValue.referTo (projectRoot, Ids::aaxIdentifier, getUndoManager(), getDefaultAAXIdentifierString());
  200. pluginAUExportPrefixValue.referTo (projectRoot, Ids::pluginAUExportPrefix, getUndoManager(),
  201. CodeHelpers::makeValidIdentifier (getProjectNameString(), false, true, false) + "AU");
  202. pluginAUMainTypeValue.referTo (projectRoot, Ids::pluginAUMainType, getUndoManager(), getDefaultAUMainTypes(), ",");
  203. pluginAUSandboxSafeValue.referTo (projectRoot, Ids::pluginAUIsSandboxSafe, getUndoManager(), false);
  204. pluginVSTCategoryValue.referTo (projectRoot, Ids::pluginVSTCategory, getUndoManager(), getDefaultVSTCategories(), ",");
  205. pluginVST3CategoryValue.referTo (projectRoot, Ids::pluginVST3Category, getUndoManager(), getDefaultVST3Categories(), ",");
  206. pluginRTASCategoryValue.referTo (projectRoot, Ids::pluginRTASCategory, getUndoManager(), getDefaultRTASCategories(), ",");
  207. pluginAAXCategoryValue.referTo (projectRoot, Ids::pluginAAXCategory, getUndoManager(), getDefaultAAXCategories(), ",");
  208. pluginVSTNumMidiInputsValue.referTo (projectRoot, Ids::pluginVSTNumMidiInputs, getUndoManager(), 16);
  209. pluginVSTNumMidiOutputsValue.referTo (projectRoot, Ids::pluginVSTNumMidiOutputs, getUndoManager(), 16);
  210. }
  211. void Project::updateOldStyleConfigList()
  212. {
  213. auto deprecatedConfigsList = projectRoot.getChildWithName (Ids::CONFIGURATIONS);
  214. if (deprecatedConfigsList.isValid())
  215. {
  216. projectRoot.removeChild (deprecatedConfigsList, nullptr);
  217. for (Project::ExporterIterator exporter (*this); exporter.next();)
  218. {
  219. if (exporter->getNumConfigurations() == 0)
  220. {
  221. auto newConfigs = deprecatedConfigsList.createCopy();
  222. if (! exporter->isXcode())
  223. {
  224. for (auto j = newConfigs.getNumChildren(); --j >= 0;)
  225. {
  226. auto config = newConfigs.getChild (j);
  227. config.removeProperty (Ids::osxSDK, nullptr);
  228. config.removeProperty (Ids::osxCompatibility, nullptr);
  229. config.removeProperty (Ids::osxArchitecture, nullptr);
  230. }
  231. }
  232. exporter->settings.addChild (newConfigs, 0, nullptr);
  233. }
  234. }
  235. }
  236. }
  237. void Project::moveOldPropertyFromProjectToAllExporters (Identifier name)
  238. {
  239. if (projectRoot.hasProperty (name))
  240. {
  241. for (Project::ExporterIterator exporter (*this); exporter.next();)
  242. exporter->settings.setProperty (name, projectRoot [name], nullptr);
  243. projectRoot.removeProperty (name, nullptr);
  244. }
  245. }
  246. void Project::removeDefunctExporters()
  247. {
  248. auto exporters = projectRoot.getChildWithName (Ids::EXPORTFORMATS);
  249. StringPairArray oldExporters;
  250. oldExporters.set ("ANDROID", "Android Ant Exporter");
  251. oldExporters.set ("MSVC6", "MSVC6");
  252. oldExporters.set ("VS2010", "Visual Studio 2010");
  253. oldExporters.set ("VS2012", "Visual Studio 2012");
  254. oldExporters.set ("VS2013", "Visual Studio 2013");
  255. for (auto& key : oldExporters.getAllKeys())
  256. {
  257. auto oldExporter = exporters.getChildWithName (key);
  258. if (oldExporter.isValid())
  259. {
  260. if (ProjucerApplication::getApp().isRunningCommandLine)
  261. std::cout << "WARNING! The " + oldExporters[key] + " Exporter is deprecated. The exporter will be removed from this project." << std::endl;
  262. else
  263. AlertWindow::showMessageBox (AlertWindow::WarningIcon,
  264. TRANS (oldExporters[key]),
  265. TRANS ("The " + oldExporters[key] + " Exporter is deprecated. The exporter will be removed from this project."));
  266. exporters.removeChild (oldExporter, nullptr);
  267. }
  268. }
  269. }
  270. void Project::updateOldModulePaths()
  271. {
  272. for (Project::ExporterIterator exporter (*this); exporter.next();)
  273. exporter->updateOldModulePaths();
  274. }
  275. Array<Identifier> Project::getLegacyPluginFormatIdentifiers() noexcept
  276. {
  277. static Array<Identifier> legacyPluginFormatIdentifiers { Ids::buildVST, Ids::buildVST3, Ids::buildAU, Ids::buildAUv3,
  278. Ids::buildRTAS, Ids::buildAAX, Ids::buildStandalone, Ids::enableIAA };
  279. return legacyPluginFormatIdentifiers;
  280. }
  281. Array<Identifier> Project::getLegacyPluginCharacteristicsIdentifiers() noexcept
  282. {
  283. static Array<Identifier> legacyPluginCharacteristicsIdentifiers { Ids::pluginIsSynth, Ids::pluginWantsMidiIn, Ids::pluginProducesMidiOut,
  284. Ids::pluginIsMidiEffectPlugin, Ids::pluginEditorRequiresKeys, Ids::pluginRTASDisableBypass,
  285. Ids::pluginRTASDisableMultiMono, Ids::pluginAAXDisableBypass, Ids::pluginAAXDisableMultiMono };
  286. return legacyPluginCharacteristicsIdentifiers;
  287. }
  288. void Project::coalescePluginFormatValues()
  289. {
  290. Array<var> formatsToBuild;
  291. for (auto& formatIdentifier : getLegacyPluginFormatIdentifiers())
  292. {
  293. if (projectRoot.getProperty (formatIdentifier, false))
  294. formatsToBuild.add (formatIdentifier.toString());
  295. }
  296. if (formatsToBuild.size() > 0)
  297. {
  298. if (pluginFormatsValue.isUsingDefault())
  299. {
  300. pluginFormatsValue = formatsToBuild;
  301. }
  302. else
  303. {
  304. auto formatVar = pluginFormatsValue.get();
  305. if (auto* arr = formatVar.getArray())
  306. arr->addArray (formatsToBuild);
  307. }
  308. shouldWriteLegacyPluginFormatSettings = true;
  309. }
  310. }
  311. void Project::coalescePluginCharacteristicsValues()
  312. {
  313. Array<var> pluginCharacteristics;
  314. for (auto& characteristicIdentifier : getLegacyPluginCharacteristicsIdentifiers())
  315. {
  316. if (projectRoot.getProperty (characteristicIdentifier, false))
  317. pluginCharacteristics.add (characteristicIdentifier.toString());
  318. }
  319. if (pluginCharacteristics.size() > 0)
  320. {
  321. pluginCharacteristicsValue = pluginCharacteristics;
  322. shouldWriteLegacyPluginCharacteristicsSettings = true;
  323. }
  324. }
  325. void Project::updatePluginCategories()
  326. {
  327. {
  328. auto aaxCategory = projectRoot.getProperty (Ids::pluginAAXCategory, {}).toString();
  329. if (getAllAAXCategoryVars().contains (aaxCategory))
  330. pluginAAXCategoryValue = aaxCategory;
  331. else if (getAllAAXCategoryStrings().contains (aaxCategory))
  332. pluginAAXCategoryValue = Array<var> (getAllAAXCategoryVars()[getAllAAXCategoryStrings().indexOf (aaxCategory)]);
  333. }
  334. {
  335. auto rtasCategory = projectRoot.getProperty (Ids::pluginRTASCategory, {}).toString();
  336. if (getAllRTASCategoryVars().contains (rtasCategory))
  337. pluginRTASCategoryValue = rtasCategory;
  338. else if (getAllRTASCategoryStrings().contains (rtasCategory))
  339. pluginRTASCategoryValue = Array<var> (getAllRTASCategoryVars()[getAllRTASCategoryStrings().indexOf (rtasCategory)]);
  340. }
  341. {
  342. auto vstCategory = projectRoot.getProperty (Ids::pluginVSTCategory, {}).toString();
  343. if (vstCategory.isNotEmpty() && getAllVSTCategoryStrings().contains (vstCategory))
  344. pluginVSTCategoryValue = Array<var> (vstCategory);
  345. else
  346. pluginVSTCategoryValue.resetToDefault();
  347. }
  348. {
  349. auto auMainType = projectRoot.getProperty (Ids::pluginAUMainType, {}).toString();
  350. if (auMainType.isNotEmpty())
  351. {
  352. if (getAllAUMainTypeVars().contains (auMainType))
  353. pluginAUMainTypeValue = Array<var> (auMainType);
  354. else if (getAllAUMainTypeVars().contains (auMainType.quoted ('\'')))
  355. pluginAUMainTypeValue = Array<var> (auMainType.quoted ('\''));
  356. else if (getAllAUMainTypeStrings().contains (auMainType))
  357. pluginAUMainTypeValue = Array<var> (getAllAUMainTypeVars()[getAllAUMainTypeStrings().indexOf (auMainType)]);
  358. }
  359. else
  360. {
  361. pluginAUMainTypeValue.resetToDefault();
  362. }
  363. }
  364. }
  365. void Project::writeLegacyPluginFormatSettings()
  366. {
  367. if (pluginFormatsValue.isUsingDefault())
  368. {
  369. for (auto& formatIdentifier : getLegacyPluginFormatIdentifiers())
  370. projectRoot.removeProperty (formatIdentifier, nullptr);
  371. }
  372. else
  373. {
  374. auto formatVar = pluginFormatsValue.get();
  375. if (auto* arr = formatVar.getArray())
  376. {
  377. for (auto& formatIdentifier : getLegacyPluginFormatIdentifiers())
  378. projectRoot.setProperty (formatIdentifier, arr->contains (formatIdentifier.toString()), nullptr);
  379. }
  380. }
  381. }
  382. void Project::writeLegacyPluginCharacteristicsSettings()
  383. {
  384. if (pluginFormatsValue.isUsingDefault())
  385. {
  386. for (auto& characteristicIdentifier : getLegacyPluginCharacteristicsIdentifiers())
  387. projectRoot.removeProperty (characteristicIdentifier, nullptr);
  388. }
  389. else
  390. {
  391. auto characteristicsVar = pluginCharacteristicsValue.get();
  392. if (auto* arr = characteristicsVar.getArray())
  393. {
  394. for (auto& characteristicIdentifier : getLegacyPluginCharacteristicsIdentifiers())
  395. projectRoot.setProperty (characteristicIdentifier, arr->contains (characteristicIdentifier.toString()), nullptr);
  396. }
  397. }
  398. }
  399. //==============================================================================
  400. static int getVersionElement (StringRef v, int index)
  401. {
  402. StringArray parts = StringArray::fromTokens (v, "., ", {});
  403. return parts [parts.size() - index - 1].getIntValue();
  404. }
  405. static int getJuceVersion (const String& v)
  406. {
  407. return getVersionElement (v, 2) * 100000
  408. + getVersionElement (v, 1) * 1000
  409. + getVersionElement (v, 0);
  410. }
  411. static constexpr int getBuiltJuceVersion()
  412. {
  413. return JUCE_MAJOR_VERSION * 100000
  414. + JUCE_MINOR_VERSION * 1000
  415. + JUCE_BUILDNUMBER;
  416. }
  417. static bool isModuleNewerThanProjucer (const ModuleDescription& module)
  418. {
  419. return module.getID().startsWith ("juce_") && getJuceVersion (module.getVersion()) > getBuiltJuceVersion();
  420. }
  421. void Project::warnAboutOldProjucerVersion()
  422. {
  423. for (auto& juceModule : ProjucerApplication::getApp().getJUCEPathModuleList().getAllModules())
  424. {
  425. if (isModuleNewerThanProjucer ({ juceModule.second }))
  426. {
  427. if (ProjucerApplication::getApp().isRunningCommandLine)
  428. std::cout << "WARNING! This version of the Projucer is out-of-date!" << std::endl;
  429. else
  430. AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
  431. "Projucer",
  432. "This version of the Projucer is out-of-date!"
  433. "\n\n"
  434. "Always make sure that you're running the very latest version, "
  435. "preferably compiled directly from the JUCE repository that you're working with!");
  436. return;
  437. }
  438. }
  439. }
  440. //==============================================================================
  441. static File lastDocumentOpened;
  442. File Project::getLastDocumentOpened() { return lastDocumentOpened; }
  443. void Project::setLastDocumentOpened (const File& file) { lastDocumentOpened = file; }
  444. static void registerRecentFile (const File& file)
  445. {
  446. RecentlyOpenedFilesList::registerRecentFileNatively (file);
  447. getAppSettings().recentFiles.addFile (file);
  448. getAppSettings().flush();
  449. }
  450. static void forgetRecentFile (const File& file)
  451. {
  452. RecentlyOpenedFilesList::forgetRecentFileNatively (file);
  453. getAppSettings().recentFiles.removeFile (file);
  454. getAppSettings().flush();
  455. }
  456. //==============================================================================
  457. Result Project::loadDocument (const File& file)
  458. {
  459. auto xml = parseXMLIfTagMatches (file, Ids::JUCERPROJECT.toString());
  460. if (xml == nullptr)
  461. return Result::fail ("Not a valid Jucer project!");
  462. auto newTree = ValueTree::fromXml (*xml);
  463. if (! newTree.hasType (Ids::JUCERPROJECT))
  464. return Result::fail ("The document contains errors and couldn't be parsed!");
  465. registerRecentFile (file);
  466. enabledModuleList.reset();
  467. projectRoot = newTree;
  468. initialiseProjectValues();
  469. initialiseMainGroup();
  470. initialiseAudioPluginValues();
  471. coalescePluginFormatValues();
  472. coalescePluginCharacteristicsValues();
  473. updatePluginCategories();
  474. parsedPreprocessorDefs = parsePreprocessorDefs (preprocessorDefsValue.get());
  475. removeDefunctExporters();
  476. updateOldModulePaths();
  477. setChangedFlag (false);
  478. if (! ProjucerApplication::getApp().isRunningCommandLine)
  479. warnAboutOldProjucerVersion();
  480. compileEngineSettings.reset (new CompileEngineSettings (projectRoot));
  481. exporterPathsModuleList.reset (new AvailableModuleList());
  482. rescanExporterPathModules (! ProjucerApplication::getApp().isRunningCommandLine);
  483. return Result::ok();
  484. }
  485. Result Project::saveDocument (const File& file)
  486. {
  487. return saveProject (file, false);
  488. }
  489. Result Project::saveProject (const File& file, bool isCommandLineApp)
  490. {
  491. if (isSaving)
  492. return Result::ok();
  493. if (isTemporaryProject())
  494. {
  495. askUserWhereToSaveProject();
  496. return Result::ok();
  497. }
  498. updateProjectSettings();
  499. if (! isCommandLineApp)
  500. {
  501. ProjucerApplication::getApp().openDocumentManager.saveAll();
  502. if (! isTemporaryProject())
  503. registerRecentFile (file);
  504. }
  505. const ScopedValueSetter<bool> vs (isSaving, true, false);
  506. ProjectSaver saver (*this, file);
  507. return saver.save (! isCommandLineApp, shouldWaitAfterSaving, specifiedExporterToSave);
  508. }
  509. Result Project::saveResourcesOnly (const File& file)
  510. {
  511. ProjectSaver saver (*this, file);
  512. return saver.saveResourcesOnly();
  513. }
  514. //==============================================================================
  515. void Project::setTemporaryDirectory (const File& dir) noexcept
  516. {
  517. tempDirectory = dir;
  518. // remove this file from the recent documents list as it is a temporary project
  519. forgetRecentFile (getFile());
  520. }
  521. void Project::askUserWhereToSaveProject()
  522. {
  523. FileChooser fc ("Save Project");
  524. fc.browseForDirectory();
  525. if (fc.getResult().exists())
  526. moveTemporaryDirectory (fc.getResult());
  527. }
  528. void Project::moveTemporaryDirectory (const File& newParentDirectory)
  529. {
  530. auto newDirectory = newParentDirectory.getChildFile (tempDirectory.getFileName());
  531. auto oldJucerFileName = getFile().getFileName();
  532. saveProjectRootToFile();
  533. tempDirectory.copyDirectoryTo (newDirectory);
  534. tempDirectory.deleteRecursively();
  535. tempDirectory = File();
  536. // reload project from new location
  537. if (auto* window = ProjucerApplication::getApp().mainWindowList.getMainWindowForFile (getFile()))
  538. {
  539. Component::SafePointer<MainWindow> safeWindow (window);
  540. MessageManager::callAsync ([safeWindow, newDirectory, oldJucerFileName]
  541. {
  542. if (safeWindow != nullptr)
  543. safeWindow.getComponent()->moveProject (newDirectory.getChildFile (oldJucerFileName));
  544. });
  545. }
  546. }
  547. bool Project::saveProjectRootToFile()
  548. {
  549. if (auto xml = projectRoot.createXml())
  550. {
  551. MemoryOutputStream mo;
  552. xml->writeTo (mo, {});
  553. return FileHelpers::overwriteFileWithNewDataIfDifferent (getFile(), mo);
  554. }
  555. jassertfalse;
  556. return false;
  557. }
  558. //==============================================================================
  559. static void sendProjectSettingAnalyticsEvent (StringRef label)
  560. {
  561. StringPairArray data;
  562. data.set ("label", label);
  563. Analytics::getInstance()->logEvent ("Project Setting", data, ProjucerAnalyticsEvent::projectEvent);
  564. }
  565. void Project::valueTreePropertyChanged (ValueTree& tree, const Identifier& property)
  566. {
  567. if (tree.getRoot() == tree)
  568. {
  569. if (property == Ids::projectType)
  570. {
  571. sendProjectSettingAnalyticsEvent ("Project Type = " + projectTypeValue.get().toString());
  572. }
  573. else if (property == Ids::name)
  574. {
  575. updateTitleDependencies();
  576. }
  577. else if (property == Ids::companyName)
  578. {
  579. updateCompanyNameDependencies();
  580. }
  581. else if (property == Ids::defines)
  582. {
  583. parsedPreprocessorDefs = parsePreprocessorDefs (preprocessorDefsValue.get());
  584. }
  585. else if (property == Ids::cppLanguageStandard)
  586. {
  587. sendProjectSettingAnalyticsEvent ("C++ Standard = " + cppStandardValue.get().toString());
  588. }
  589. else if (property == Ids::pluginFormats)
  590. {
  591. if (shouldWriteLegacyPluginFormatSettings)
  592. writeLegacyPluginFormatSettings();
  593. }
  594. else if (property == Ids::pluginCharacteristicsValue)
  595. {
  596. pluginAUMainTypeValue.setDefault (getDefaultAUMainTypes());
  597. pluginVSTCategoryValue.setDefault (getDefaultVSTCategories());
  598. pluginVST3CategoryValue.setDefault (getDefaultVST3Categories());
  599. pluginRTASCategoryValue.setDefault (getDefaultRTASCategories());
  600. pluginAAXCategoryValue.setDefault (getDefaultAAXCategories());
  601. if (shouldWriteLegacyPluginCharacteristicsSettings)
  602. writeLegacyPluginCharacteristicsSettings();
  603. }
  604. changed();
  605. }
  606. }
  607. void Project::valueTreeChildAdded (ValueTree&, ValueTree&) { changed(); }
  608. void Project::valueTreeChildRemoved (ValueTree&, ValueTree&, int) { changed(); }
  609. void Project::valueTreeChildOrderChanged (ValueTree&, int, int) { changed(); }
  610. //==============================================================================
  611. bool Project::hasProjectBeenModified()
  612. {
  613. auto oldModificationTime = modificationTime;
  614. modificationTime = getFile().getLastModificationTime();
  615. return (modificationTime.toMilliseconds() > (oldModificationTime.toMilliseconds() + 1000LL));
  616. }
  617. //==============================================================================
  618. File Project::resolveFilename (String filename) const
  619. {
  620. if (filename.isEmpty())
  621. return {};
  622. filename = replacePreprocessorDefs (getPreprocessorDefs(), filename);
  623. #if ! JUCE_WINDOWS
  624. if (filename.startsWith ("~"))
  625. return File::getSpecialLocation (File::userHomeDirectory).getChildFile (filename.trimCharactersAtStart ("~/"));
  626. #endif
  627. if (FileHelpers::isAbsolutePath (filename))
  628. return File::createFileWithoutCheckingPath (FileHelpers::currentOSStylePath (filename)); // (avoid assertions for windows-style paths)
  629. return getFile().getSiblingFile (FileHelpers::currentOSStylePath (filename));
  630. }
  631. String Project::getRelativePathForFile (const File& file) const
  632. {
  633. auto filename = file.getFullPathName();
  634. auto relativePathBase = getFile().getParentDirectory();
  635. auto p1 = relativePathBase.getFullPathName();
  636. auto p2 = file.getFullPathName();
  637. while (p1.startsWithChar (File::getSeparatorChar()))
  638. p1 = p1.substring (1);
  639. while (p2.startsWithChar (File::getSeparatorChar()))
  640. p2 = p2.substring (1);
  641. if (p1.upToFirstOccurrenceOf (File::getSeparatorString(), true, false)
  642. .equalsIgnoreCase (p2.upToFirstOccurrenceOf (File::getSeparatorString(), true, false)))
  643. {
  644. filename = FileHelpers::getRelativePathFrom (file, relativePathBase);
  645. }
  646. return filename;
  647. }
  648. //==============================================================================
  649. const ProjectType& Project::getProjectType() const
  650. {
  651. if (auto* type = ProjectType::findType (getProjectTypeString()))
  652. return *type;
  653. auto* guiType = ProjectType::findType (ProjectType_GUIApp::getTypeName());
  654. jassert (guiType != nullptr);
  655. return *guiType;
  656. }
  657. bool Project::shouldBuildTargetType (ProjectType::Target::Type targetType) const noexcept
  658. {
  659. auto& projectType = getProjectType();
  660. if (! projectType.supportsTargetType (targetType))
  661. return false;
  662. switch (targetType)
  663. {
  664. case ProjectType::Target::VSTPlugIn:
  665. return shouldBuildVST();
  666. case ProjectType::Target::VST3PlugIn:
  667. return shouldBuildVST3();
  668. case ProjectType::Target::AAXPlugIn:
  669. return shouldBuildAAX();
  670. case ProjectType::Target::RTASPlugIn:
  671. return shouldBuildRTAS();
  672. case ProjectType::Target::AudioUnitPlugIn:
  673. return shouldBuildAU();
  674. case ProjectType::Target::AudioUnitv3PlugIn:
  675. return shouldBuildAUv3();
  676. case ProjectType::Target::StandalonePlugIn:
  677. return shouldBuildStandalonePlugin();
  678. case ProjectType::Target::UnityPlugIn:
  679. return shouldBuildUnityPlugin();
  680. case ProjectType::Target::AggregateTarget:
  681. case ProjectType::Target::SharedCodeTarget:
  682. return projectType.isAudioPlugin();
  683. case ProjectType::Target::unspecified:
  684. return false;
  685. case ProjectType::Target::GUIApp:
  686. case ProjectType::Target::ConsoleApp:
  687. case ProjectType::Target::StaticLibrary:
  688. case ProjectType::Target::DynamicLibrary:
  689. default:
  690. break;
  691. }
  692. return true;
  693. }
  694. ProjectType::Target::Type Project::getTargetTypeFromFilePath (const File& file, bool returnSharedTargetIfNoValidSuffix)
  695. {
  696. if (LibraryModule::CompileUnit::hasSuffix (file, "_AU")) return ProjectType::Target::AudioUnitPlugIn;
  697. else if (LibraryModule::CompileUnit::hasSuffix (file, "_AUv3")) return ProjectType::Target::AudioUnitv3PlugIn;
  698. else if (LibraryModule::CompileUnit::hasSuffix (file, "_AAX")) return ProjectType::Target::AAXPlugIn;
  699. else if (LibraryModule::CompileUnit::hasSuffix (file, "_RTAS")) return ProjectType::Target::RTASPlugIn;
  700. else if (LibraryModule::CompileUnit::hasSuffix (file, "_VST2")) return ProjectType::Target::VSTPlugIn;
  701. else if (LibraryModule::CompileUnit::hasSuffix (file, "_VST3")) return ProjectType::Target::VST3PlugIn;
  702. else if (LibraryModule::CompileUnit::hasSuffix (file, "_Standalone")) return ProjectType::Target::StandalonePlugIn;
  703. else if (LibraryModule::CompileUnit::hasSuffix (file, "_Unity")) return ProjectType::Target::UnityPlugIn;
  704. return (returnSharedTargetIfNoValidSuffix ? ProjectType::Target::SharedCodeTarget : ProjectType::Target::unspecified);
  705. }
  706. const char* ProjectType::Target::getName() const noexcept
  707. {
  708. switch (type)
  709. {
  710. case GUIApp: return "App";
  711. case ConsoleApp: return "ConsoleApp";
  712. case StaticLibrary: return "Static Library";
  713. case DynamicLibrary: return "Dynamic Library";
  714. case VSTPlugIn: return "VST";
  715. case VST3PlugIn: return "VST3";
  716. case AudioUnitPlugIn: return "AU";
  717. case StandalonePlugIn: return "Standalone Plugin";
  718. case AudioUnitv3PlugIn: return "AUv3 AppExtension";
  719. case AAXPlugIn: return "AAX";
  720. case RTASPlugIn: return "RTAS";
  721. case UnityPlugIn: return "Unity Plugin";
  722. case SharedCodeTarget: return "Shared Code";
  723. case AggregateTarget: return "All";
  724. case unspecified:
  725. default: return "undefined";
  726. }
  727. }
  728. ProjectType::Target::TargetFileType ProjectType::Target::getTargetFileType() const noexcept
  729. {
  730. switch (type)
  731. {
  732. case GUIApp: return executable;
  733. case ConsoleApp: return executable;
  734. case StaticLibrary: return staticLibrary;
  735. case DynamicLibrary: return sharedLibraryOrDLL;
  736. case VSTPlugIn: return pluginBundle;
  737. case VST3PlugIn: return pluginBundle;
  738. case AudioUnitPlugIn: return pluginBundle;
  739. case StandalonePlugIn: return executable;
  740. case AudioUnitv3PlugIn: return macOSAppex;
  741. case AAXPlugIn: return pluginBundle;
  742. case RTASPlugIn: return pluginBundle;
  743. case UnityPlugIn: return pluginBundle;
  744. case SharedCodeTarget: return staticLibrary;
  745. case AggregateTarget:
  746. case unspecified:
  747. default:
  748. break;
  749. }
  750. return unknown;
  751. }
  752. //==============================================================================
  753. void Project::createPropertyEditors (PropertyListBuilder& props)
  754. {
  755. props.add (new TextPropertyComponent (projectNameValue, "Project Name", 256, false),
  756. "The name of the project.");
  757. props.add (new TextPropertyComponent (versionValue, "Project Version", 16, false),
  758. "The project's version number. This should be in the format major.minor.point[.point] where you should omit the final "
  759. "(optional) [.point] if you are targeting AU and AUv3 plug-ins as they only support three number versions.");
  760. props.add (new ChoicePropertyComponent (projectLineFeedValue, "Project Line Feed", { "\\r\\n", "\\n", }, { "\r\n", "\n" }),
  761. "Use this to set the line feed which will be used when creating new source files for this project "
  762. "(this won't affect any existing files).");
  763. props.add (new TextPropertyComponent (companyNameValue, "Company Name", 256, false),
  764. "Your company name, which will be added to the properties of the binary where possible");
  765. props.add (new TextPropertyComponent (companyCopyrightValue, "Company Copyright", 256, false),
  766. "Your company copyright, which will be added to the properties of the binary where possible");
  767. props.add (new TextPropertyComponent (companyWebsiteValue, "Company Website", 256, false),
  768. "Your company website, which will be added to the properties of the binary where possible");
  769. props.add (new TextPropertyComponent (companyEmailValue, "Company E-mail", 256, false),
  770. "Your company e-mail, which will be added to the properties of the binary where possible");
  771. {
  772. String licenseRequiredTagline ("Required for closed source applications without an Indie or Pro JUCE license");
  773. String licenseRequiredInfo ("In accordance with the terms of the JUCE 5 End-Use License Agreement (www.juce.com/juce-5-licence), "
  774. "this option can only be disabled for closed source applications if you have a JUCE Indie or Pro "
  775. "license, or are using JUCE under the GPL v3 license.");
  776. StringPairArray description;
  777. description.set ("Report JUCE app usage", "This option controls the collection of usage data from users of this JUCE application.");
  778. description.set ("Display the JUCE splash screen", "This option controls the display of the standard JUCE splash screen.");
  779. if (ProjucerApplication::getApp().isPaidOrGPL())
  780. {
  781. props.add (new ChoicePropertyComponent (reportAppUsageValue, String ("Report JUCE App Usage") + " (" + licenseRequiredTagline + ")"),
  782. description["Report JUCE app usage"] + " " + licenseRequiredInfo);
  783. props.add (new ChoicePropertyComponent (displaySplashScreenValue, String ("Display the JUCE Splash Screen") + " (" + licenseRequiredTagline + ")"),
  784. description["Display the JUCE splash screen"] + " " + licenseRequiredInfo);
  785. }
  786. else
  787. {
  788. StringArray options;
  789. Array<var> vars;
  790. options.add (licenseRequiredTagline);
  791. vars.add (var());
  792. props.add (new ChoicePropertyComponent (Value(), "Report JUCE App Usage", options, vars),
  793. description["Report JUCE app usage"] + " " + licenseRequiredInfo);
  794. props.add (new ChoicePropertyComponent (Value(), "Display the JUCE Splash Screen", options, vars),
  795. description["Display the JUCE splash screen"] + " " + licenseRequiredInfo);
  796. }
  797. }
  798. props.add (new ChoicePropertyComponent (splashScreenColourValue, "Splash Screen Colour",
  799. { "Dark", "Light" },
  800. { "Dark", "Light" }),
  801. "Choose the colour of the JUCE splash screen.");
  802. {
  803. StringArray projectTypeNames;
  804. Array<var> projectTypeCodes;
  805. auto types = ProjectType::getAllTypes();
  806. for (int i = 0; i < types.size(); ++i)
  807. {
  808. projectTypeNames.add (types.getUnchecked(i)->getDescription());
  809. projectTypeCodes.add (types.getUnchecked(i)->getType());
  810. }
  811. props.add (new ChoicePropertyComponent (projectTypeValue, "Project Type", projectTypeNames, projectTypeCodes),
  812. "The project type for which settings should be shown.");
  813. }
  814. props.add (new TextPropertyComponent (bundleIdentifierValue, "Bundle Identifier", 256, false),
  815. "A unique identifier for this product, mainly for use in OSX/iOS builds. It should be something like 'com.yourcompanyname.yourproductname'");
  816. if (isAudioPluginProject())
  817. createAudioPluginPropertyEditors (props);
  818. {
  819. const int maxSizes[] = { 20480, 10240, 6144, 2048, 1024, 512, 256, 128, 64 };
  820. StringArray maxSizeNames;
  821. Array<var> maxSizeCodes;
  822. for (int i = 0; i < numElementsInArray (maxSizes); ++i)
  823. {
  824. auto sizeInBytes = maxSizes[i] * 1024;
  825. maxSizeNames.add (File::descriptionOfSizeInBytes (sizeInBytes));
  826. maxSizeCodes.add (sizeInBytes);
  827. }
  828. props.add (new ChoicePropertyComponent (maxBinaryFileSizeValue, "BinaryData.cpp Size Limit", maxSizeNames, maxSizeCodes),
  829. "When splitting binary data into multiple cpp files, the Projucer attempts to keep the file sizes below this threshold. "
  830. "(Note that individual resource files which are larger than this size cannot be split across multiple cpp files).");
  831. }
  832. props.add (new ChoicePropertyComponent (includeBinaryDataInJuceHeaderValue, "Include BinaryData in JuceHeader"),
  833. "Include BinaryData.h in the JuceHeader.h file");
  834. props.add (new TextPropertyComponent (binaryDataNamespaceValue, "BinaryData Namespace", 256, false),
  835. "The namespace containing the binary assets.");
  836. props.add (new ChoicePropertyComponent (cppStandardValue, "C++ Language Standard",
  837. { "C++11", "C++14", "C++17", "Use Latest" },
  838. { "11", "14", "17", "latest" }),
  839. "The standard of the C++ language that will be used for compilation.");
  840. props.add (new TextPropertyComponent (preprocessorDefsValue, "Preprocessor Definitions", 32768, true),
  841. "Global preprocessor definitions. Use the form \"NAME1=value NAME2=value\", using whitespace, commas, or "
  842. "new-lines to separate the items - to include a space or comma in a definition, precede it with a backslash.");
  843. props.addSearchPathProperty (headerSearchPathsValue, "Header Search Paths", "Global header search paths.");
  844. props.add (new TextPropertyComponent (postExportShellCommandPosixValue, "Post-Export Shell Command (macOS, Linux)", 1024, false),
  845. "A command that will be executed by the system shell after saving this project on macOS or Linux. "
  846. "The string \"%%1%%\" will be substituted with the absolute path to the project root folder.");
  847. props.add (new TextPropertyComponent (postExportShellCommandWinValue, "Post-Export Shell Command (Windows)", 1024, false),
  848. "A command that will be executed by the system shell after saving this project on Windows. "
  849. "The string \"%%1%%\" will be substituted with the absolute path to the project root folder.");
  850. props.add (new TextPropertyComponent (userNotesValue, "Notes", 32768, true),
  851. "Extra comments: This field is not used for code or project generation, it's just a space where you can express your thoughts.");
  852. }
  853. void Project::createAudioPluginPropertyEditors (PropertyListBuilder& props)
  854. {
  855. props.add (new MultiChoicePropertyComponent (pluginFormatsValue, "Plugin Formats",
  856. { "VST3", "AU", "AUv3", "RTAS (deprecated)", "AAX", "Standalone", "Unity", "Enable IAA", "VST (Legacy)" },
  857. { Ids::buildVST3.toString(), Ids::buildAU.toString(), Ids::buildAUv3.toString(),
  858. Ids::buildRTAS.toString(), Ids::buildAAX.toString(), Ids::buildStandalone.toString(), Ids::buildUnity.toString(),
  859. Ids::enableIAA.toString(), Ids::buildVST.toString() }),
  860. "Plugin formats to build. If you have selected \"VST (Legacy)\" then you will need to ensure that you have a VST2 SDK "
  861. "in your header search paths. The VST2 SDK can be obtained from the vstsdk3610_11_06_2018_build_37 (or older) VST3 SDK "
  862. "or JUCE version 5.3.2. You also need a VST2 license from Steinberg to distribute VST2 plug-ins.");
  863. props.add (new MultiChoicePropertyComponent (pluginCharacteristicsValue, "Plugin Characteristics",
  864. { "Plugin is a Synth", "Plugin MIDI Input", "Plugin MIDI Output", "MIDI Effect Plugin", "Plugin Editor Requires Keyboard Focus",
  865. "Disable RTAS Bypass", "Disable AAX Bypass", "Disable RTAS Multi-Mono", "Disable AAX Multi-Mono" },
  866. { Ids::pluginIsSynth.toString(), Ids::pluginWantsMidiIn.toString(), Ids::pluginProducesMidiOut.toString(),
  867. Ids::pluginIsMidiEffectPlugin.toString(), Ids::pluginEditorRequiresKeys.toString(), Ids::pluginRTASDisableBypass.toString(),
  868. Ids::pluginAAXDisableBypass.toString(), Ids::pluginRTASDisableMultiMono.toString(), Ids::pluginAAXDisableMultiMono.toString() }),
  869. "Some characteristics of your plugin such as whether it is a synth, produces MIDI messages, accepts MIDI messages etc.");
  870. props.add (new TextPropertyComponent (pluginNameValue, "Plugin Name", 128, false),
  871. "The name of your plugin (keep it short!)");
  872. props.add (new TextPropertyComponent (pluginDescriptionValue, "Plugin Description", 256, false),
  873. "A short description of your plugin.");
  874. props.add (new TextPropertyComponent (pluginManufacturerValue, "Plugin Manufacturer", 256, false),
  875. "The name of your company (cannot be blank).");
  876. props.add (new TextPropertyComponent (pluginManufacturerCodeValue, "Plugin Manufacturer Code", 4, false),
  877. "A four-character unique ID for your company. Note that for AU compatibility, this must contain at least one upper-case letter!");
  878. props.add (new TextPropertyComponent (pluginCodeValue, "Plugin Code", 4, false),
  879. "A four-character unique ID for your plugin. Note that for AU compatibility, this must contain at least one upper-case letter!");
  880. props.add (new TextPropertyComponent (pluginChannelConfigsValue, "Plugin Channel Configurations", 1024, false),
  881. "This list is a comma-separated set list in the form {numIns, numOuts} and each pair indicates a valid plug-in "
  882. "configuration. For example {1, 1}, {2, 2} means that the plugin can be used either with 1 input and 1 output, "
  883. "or with 2 inputs and 2 outputs. If your plug-in requires side-chains, aux output buses etc., then you must leave "
  884. "this field empty and override the isBusesLayoutSupported callback in your AudioProcessor.");
  885. props.add (new TextPropertyComponent (pluginAAXIdentifierValue, "Plugin AAX Identifier", 256, false),
  886. "The value to use for the JucePlugin_AAXIdentifier setting");
  887. props.add (new TextPropertyComponent (pluginAUExportPrefixValue, "Plugin AU Export Prefix", 128, false),
  888. "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.");
  889. props.add (new MultiChoicePropertyComponent (pluginAUMainTypeValue, "Plugin AU Main Type", getAllAUMainTypeStrings(), getAllAUMainTypeVars(), 1),
  890. "AU main type.");
  891. props.add (new ChoicePropertyComponent (pluginAUSandboxSafeValue, "Plugin AU is sandbox safe"),
  892. "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 "
  893. "the Music folder. Your plug-in must be able to deal with this. Newer versions of GarageBand require this to be enabled.");
  894. {
  895. Array<var> varChoices;
  896. StringArray stringChoices;
  897. for (int i = 1; i <= 16; ++i)
  898. {
  899. varChoices.add (i);
  900. stringChoices.add (String (i));
  901. }
  902. props.add (new ChoicePropertyComponentWithEnablement (pluginVSTNumMidiInputsValue, pluginCharacteristicsValue, Ids::pluginWantsMidiIn,
  903. "Plugin VST Num MIDI Inputs", stringChoices, varChoices),
  904. "For VST and VST3 plug-ins that accept MIDI, this allows you to configure the number of inputs.");
  905. props.add (new ChoicePropertyComponentWithEnablement (pluginVSTNumMidiOutputsValue, pluginCharacteristicsValue, Ids::pluginProducesMidiOut,
  906. "Plugin VST Num MIDI Outputs", stringChoices, varChoices),
  907. "For VST and VST3 plug-ins that produce MIDI, this allows you to configure the number of outputs.");
  908. }
  909. {
  910. Array<var> vst3CategoryVars;
  911. for (auto s : getAllVST3CategoryStrings())
  912. vst3CategoryVars.add (s);
  913. props.add (new MultiChoicePropertyComponent (pluginVST3CategoryValue, "Plugin VST3 Category", getAllVST3CategoryStrings(), vst3CategoryVars),
  914. "VST3 category. Most hosts require either \"Fx\" or \"Instrument\" to be selected in order for the plugin to be recognised. "
  915. "If neither of these are selected, the appropriate one will be automatically added based on the \"Plugin is a synth\" option.");
  916. }
  917. props.add (new MultiChoicePropertyComponent (pluginRTASCategoryValue, "Plugin RTAS Category", getAllRTASCategoryStrings(), getAllRTASCategoryVars()),
  918. "RTAS category.");
  919. props.add (new MultiChoicePropertyComponent (pluginAAXCategoryValue, "Plugin AAX Category", getAllAAXCategoryStrings(), getAllAAXCategoryVars()),
  920. "AAX category.");
  921. {
  922. Array<var> vstCategoryVars;
  923. for (auto s : getAllVSTCategoryStrings())
  924. vstCategoryVars.add (s);
  925. props.add (new MultiChoicePropertyComponent (pluginVSTCategoryValue, "Plugin VST (Legacy) Category", getAllVSTCategoryStrings(), vstCategoryVars, 1),
  926. "VST category.");
  927. }
  928. }
  929. //==============================================================================
  930. static StringArray getVersionSegments (const Project& p)
  931. {
  932. auto segments = StringArray::fromTokens (p.getVersionString(), ",.", "");
  933. segments.trim();
  934. segments.removeEmptyStrings();
  935. return segments;
  936. }
  937. int Project::getVersionAsHexInteger() const
  938. {
  939. auto segments = getVersionSegments (*this);
  940. auto value = (segments[0].getIntValue() << 16)
  941. + (segments[1].getIntValue() << 8)
  942. + segments[2].getIntValue();
  943. if (segments.size() > 3)
  944. value = (value << 8) + segments[3].getIntValue();
  945. return value;
  946. }
  947. String Project::getVersionAsHex() const
  948. {
  949. return "0x" + String::toHexString (getVersionAsHexInteger());
  950. }
  951. File Project::getBinaryDataCppFile (int index) const
  952. {
  953. auto cpp = getGeneratedCodeFolder().getChildFile ("BinaryData.cpp");
  954. if (index > 0)
  955. return cpp.getSiblingFile (cpp.getFileNameWithoutExtension() + String (index + 1))
  956. .withFileExtension (cpp.getFileExtension());
  957. return cpp;
  958. }
  959. Project::Item Project::getMainGroup()
  960. {
  961. return { *this, projectRoot.getChildWithName (Ids::MAINGROUP), false };
  962. }
  963. PropertiesFile& Project::getStoredProperties() const
  964. {
  965. return getAppSettings().getProjectProperties (getProjectUIDString());
  966. }
  967. static void findImages (const Project::Item& item, OwnedArray<Project::Item>& found)
  968. {
  969. if (item.isImageFile())
  970. {
  971. found.add (new Project::Item (item));
  972. }
  973. else if (item.isGroup())
  974. {
  975. for (int i = 0; i < item.getNumChildren(); ++i)
  976. findImages (item.getChild (i), found);
  977. }
  978. }
  979. void Project::findAllImageItems (OwnedArray<Project::Item>& items)
  980. {
  981. findImages (getMainGroup(), items);
  982. }
  983. //==============================================================================
  984. Project::Item::Item (Project& p, const ValueTree& s, bool isModuleCode)
  985. : project (p), state (s), belongsToModule (isModuleCode)
  986. {
  987. }
  988. Project::Item::Item (const Item& other)
  989. : project (other.project), state (other.state), belongsToModule (other.belongsToModule)
  990. {
  991. }
  992. Project::Item Project::Item::createCopy() { Item i (*this); i.state = i.state.createCopy(); return i; }
  993. String Project::Item::getID() const { return state [Ids::ID]; }
  994. void Project::Item::setID (const String& newID) { state.setProperty (Ids::ID, newID, nullptr); }
  995. std::unique_ptr<Drawable> Project::Item::loadAsImageFile() const
  996. {
  997. const MessageManagerLock mml (ThreadPoolJob::getCurrentThreadPoolJob());
  998. if (! mml.lockWasGained())
  999. return nullptr;
  1000. if (isValid())
  1001. return Drawable::createFromImageFile (getFile());
  1002. return {};
  1003. }
  1004. Project::Item Project::Item::createGroup (Project& project, const String& name, const String& uid, bool isModuleCode)
  1005. {
  1006. Item group (project, ValueTree (Ids::GROUP), isModuleCode);
  1007. group.setID (uid);
  1008. group.initialiseMissingProperties();
  1009. group.getNameValue() = name;
  1010. return group;
  1011. }
  1012. bool Project::Item::isFile() const { return state.hasType (Ids::FILE); }
  1013. bool Project::Item::isGroup() const { return state.hasType (Ids::GROUP) || isMainGroup(); }
  1014. bool Project::Item::isMainGroup() const { return state.hasType (Ids::MAINGROUP); }
  1015. bool Project::Item::isImageFile() const
  1016. {
  1017. return isFile() && (ImageFileFormat::findImageFormatForFileExtension (getFile()) != nullptr
  1018. || getFile().hasFileExtension ("svg"));
  1019. }
  1020. Project::Item Project::Item::findItemWithID (const String& targetId) const
  1021. {
  1022. if (state [Ids::ID] == targetId)
  1023. return *this;
  1024. if (isGroup())
  1025. {
  1026. for (auto i = getNumChildren(); --i >= 0;)
  1027. {
  1028. auto found = getChild(i).findItemWithID (targetId);
  1029. if (found.isValid())
  1030. return found;
  1031. }
  1032. }
  1033. return Item (project, ValueTree(), false);
  1034. }
  1035. bool Project::Item::canContain (const Item& child) const
  1036. {
  1037. if (isFile())
  1038. return false;
  1039. if (isGroup())
  1040. return child.isFile() || child.isGroup();
  1041. jassertfalse;
  1042. return false;
  1043. }
  1044. bool Project::Item::shouldBeAddedToTargetProject() const { return isFile(); }
  1045. bool Project::Item::shouldBeAddedToTargetExporter (const ProjectExporter& exporter) const
  1046. {
  1047. if (shouldBeAddedToXcodeResources())
  1048. return exporter.isXcode() || shouldBeCompiled();
  1049. return true;
  1050. }
  1051. Value Project::Item::getShouldCompileValue() { return state.getPropertyAsValue (Ids::compile, getUndoManager()); }
  1052. bool Project::Item::shouldBeCompiled() const { return state [Ids::compile]; }
  1053. Value Project::Item::getShouldAddToBinaryResourcesValue() { return state.getPropertyAsValue (Ids::resource, getUndoManager()); }
  1054. bool Project::Item::shouldBeAddedToBinaryResources() const { return state [Ids::resource]; }
  1055. Value Project::Item::getShouldAddToXcodeResourcesValue() { return state.getPropertyAsValue (Ids::xcodeResource, getUndoManager()); }
  1056. bool Project::Item::shouldBeAddedToXcodeResources() const { return state [Ids::xcodeResource]; }
  1057. Value Project::Item::getShouldInhibitWarningsValue() { return state.getPropertyAsValue (Ids::noWarnings, getUndoManager()); }
  1058. bool Project::Item::shouldInhibitWarnings() const { return state [Ids::noWarnings]; }
  1059. bool Project::Item::isModuleCode() const { return belongsToModule; }
  1060. Value Project::Item::getCompilerFlagSchemeValue() { return state.getPropertyAsValue (Ids::compilerFlagScheme, getUndoManager()); }
  1061. String Project::Item::getCompilerFlagSchemeString() const { return state [Ids::compilerFlagScheme]; }
  1062. void Project::Item::setCompilerFlagScheme (const String& scheme)
  1063. {
  1064. state.getPropertyAsValue (Ids::compilerFlagScheme, getUndoManager()).setValue (scheme);
  1065. }
  1066. void Project::Item::clearCurrentCompilerFlagScheme()
  1067. {
  1068. state.removeProperty (Ids::compilerFlagScheme, getUndoManager());
  1069. }
  1070. String Project::Item::getFilePath() const
  1071. {
  1072. if (isFile())
  1073. return state [Ids::file].toString();
  1074. return {};
  1075. }
  1076. File Project::Item::getFile() const
  1077. {
  1078. if (isFile())
  1079. return project.resolveFilename (state [Ids::file].toString());
  1080. return {};
  1081. }
  1082. void Project::Item::setFile (const File& file)
  1083. {
  1084. setFile (RelativePath (project.getRelativePathForFile (file), RelativePath::projectFolder));
  1085. jassert (getFile() == file);
  1086. }
  1087. void Project::Item::setFile (const RelativePath& file)
  1088. {
  1089. jassert (isFile());
  1090. state.setProperty (Ids::file, file.toUnixStyle(), getUndoManager());
  1091. state.setProperty (Ids::name, file.getFileName(), getUndoManager());
  1092. }
  1093. bool Project::Item::renameFile (const File& newFile)
  1094. {
  1095. auto oldFile = getFile();
  1096. if (oldFile.moveFileTo (newFile)
  1097. || (newFile.exists() && ! oldFile.exists()))
  1098. {
  1099. setFile (newFile);
  1100. ProjucerApplication::getApp().openDocumentManager.fileHasBeenRenamed (oldFile, newFile);
  1101. return true;
  1102. }
  1103. return false;
  1104. }
  1105. bool Project::Item::containsChildForFile (const RelativePath& file) const
  1106. {
  1107. return state.getChildWithProperty (Ids::file, file.toUnixStyle()).isValid();
  1108. }
  1109. Project::Item Project::Item::findItemForFile (const File& file) const
  1110. {
  1111. if (getFile() == file)
  1112. return *this;
  1113. if (isGroup())
  1114. {
  1115. for (auto i = getNumChildren(); --i >= 0;)
  1116. {
  1117. auto found = getChild(i).findItemForFile (file);
  1118. if (found.isValid())
  1119. return found;
  1120. }
  1121. }
  1122. return Item (project, ValueTree(), false);
  1123. }
  1124. File Project::Item::determineGroupFolder() const
  1125. {
  1126. jassert (isGroup());
  1127. File f;
  1128. for (int i = 0; i < getNumChildren(); ++i)
  1129. {
  1130. f = getChild(i).getFile();
  1131. if (f.exists())
  1132. return f.getParentDirectory();
  1133. }
  1134. auto parent = getParent();
  1135. if (parent != *this)
  1136. {
  1137. f = parent.determineGroupFolder();
  1138. if (f.getChildFile (getName()).isDirectory())
  1139. f = f.getChildFile (getName());
  1140. }
  1141. else
  1142. {
  1143. f = project.getProjectFolder();
  1144. if (f.getChildFile ("Source").isDirectory())
  1145. f = f.getChildFile ("Source");
  1146. }
  1147. return f;
  1148. }
  1149. void Project::Item::initialiseMissingProperties()
  1150. {
  1151. if (! state.hasProperty (Ids::ID))
  1152. setID (createAlphaNumericUID());
  1153. if (isFile())
  1154. {
  1155. state.setProperty (Ids::name, getFile().getFileName(), nullptr);
  1156. }
  1157. else if (isGroup())
  1158. {
  1159. for (auto i = getNumChildren(); --i >= 0;)
  1160. getChild(i).initialiseMissingProperties();
  1161. }
  1162. }
  1163. Value Project::Item::getNameValue()
  1164. {
  1165. return state.getPropertyAsValue (Ids::name, getUndoManager());
  1166. }
  1167. String Project::Item::getName() const
  1168. {
  1169. return state [Ids::name];
  1170. }
  1171. void Project::Item::addChild (const Item& newChild, int insertIndex)
  1172. {
  1173. state.addChild (newChild.state, insertIndex, getUndoManager());
  1174. }
  1175. void Project::Item::removeItemFromProject()
  1176. {
  1177. state.getParent().removeChild (state, getUndoManager());
  1178. }
  1179. Project::Item Project::Item::getParent() const
  1180. {
  1181. if (isMainGroup() || ! isGroup())
  1182. return *this;
  1183. return { project, state.getParent(), belongsToModule };
  1184. }
  1185. struct ItemSorter
  1186. {
  1187. static int compareElements (const ValueTree& first, const ValueTree& second)
  1188. {
  1189. return first [Ids::name].toString().compareNatural (second [Ids::name].toString());
  1190. }
  1191. };
  1192. struct ItemSorterWithGroupsAtStart
  1193. {
  1194. static int compareElements (const ValueTree& first, const ValueTree& second)
  1195. {
  1196. auto firstIsGroup = first.hasType (Ids::GROUP);
  1197. auto secondIsGroup = second.hasType (Ids::GROUP);
  1198. if (firstIsGroup == secondIsGroup)
  1199. return first [Ids::name].toString().compareNatural (second [Ids::name].toString());
  1200. return firstIsGroup ? -1 : 1;
  1201. }
  1202. };
  1203. static void sortGroup (ValueTree& state, bool keepGroupsAtStart, UndoManager* undoManager)
  1204. {
  1205. if (keepGroupsAtStart)
  1206. {
  1207. ItemSorterWithGroupsAtStart sorter;
  1208. state.sort (sorter, undoManager, true);
  1209. }
  1210. else
  1211. {
  1212. ItemSorter sorter;
  1213. state.sort (sorter, undoManager, true);
  1214. }
  1215. }
  1216. static bool isGroupSorted (const ValueTree& state, bool keepGroupsAtStart)
  1217. {
  1218. if (state.getNumChildren() == 0)
  1219. return false;
  1220. if (state.getNumChildren() == 1)
  1221. return true;
  1222. auto stateCopy = state.createCopy();
  1223. sortGroup (stateCopy, keepGroupsAtStart, nullptr);
  1224. return stateCopy.isEquivalentTo (state);
  1225. }
  1226. void Project::Item::sortAlphabetically (bool keepGroupsAtStart, bool recursive)
  1227. {
  1228. sortGroup (state, keepGroupsAtStart, getUndoManager());
  1229. if (recursive)
  1230. for (auto i = getNumChildren(); --i >= 0;)
  1231. getChild(i).sortAlphabetically (keepGroupsAtStart, true);
  1232. }
  1233. Project::Item Project::Item::getOrCreateSubGroup (const String& name)
  1234. {
  1235. for (auto i = state.getNumChildren(); --i >= 0;)
  1236. {
  1237. auto child = state.getChild (i);
  1238. if (child.getProperty (Ids::name) == name && child.hasType (Ids::GROUP))
  1239. return { project, child, belongsToModule };
  1240. }
  1241. return addNewSubGroup (name, -1);
  1242. }
  1243. Project::Item Project::Item::addNewSubGroup (const String& name, int insertIndex)
  1244. {
  1245. auto newID = createGUID (getID() + name + String (getNumChildren()));
  1246. int n = 0;
  1247. while (project.getMainGroup().findItemWithID (newID).isValid())
  1248. newID = createGUID (newID + String (++n));
  1249. auto group = createGroup (project, name, newID, belongsToModule);
  1250. jassert (canContain (group));
  1251. addChild (group, insertIndex);
  1252. return group;
  1253. }
  1254. bool Project::Item::addFileAtIndex (const File& file, int insertIndex, const bool shouldCompile)
  1255. {
  1256. if (file == File() || file.isHidden() || file.getFileName().startsWithChar ('.'))
  1257. return false;
  1258. if (file.isDirectory())
  1259. {
  1260. auto group = addNewSubGroup (file.getFileName(), insertIndex);
  1261. for (DirectoryIterator iter (file, false, "*", File::findFilesAndDirectories); iter.next();)
  1262. if (! project.getMainGroup().findItemForFile (iter.getFile()).isValid())
  1263. group.addFileRetainingSortOrder (iter.getFile(), shouldCompile);
  1264. }
  1265. else if (file.existsAsFile())
  1266. {
  1267. if (! project.getMainGroup().findItemForFile (file).isValid())
  1268. addFileUnchecked (file, insertIndex, shouldCompile);
  1269. }
  1270. else
  1271. {
  1272. jassertfalse;
  1273. }
  1274. return true;
  1275. }
  1276. bool Project::Item::addFileRetainingSortOrder (const File& file, bool shouldCompile)
  1277. {
  1278. auto wasSortedGroupsNotFirst = isGroupSorted (state, false);
  1279. auto wasSortedGroupsFirst = isGroupSorted (state, true);
  1280. if (! addFileAtIndex (file, 0, shouldCompile))
  1281. return false;
  1282. if (wasSortedGroupsNotFirst || wasSortedGroupsFirst)
  1283. sortAlphabetically (wasSortedGroupsFirst, false);
  1284. return true;
  1285. }
  1286. void Project::Item::addFileUnchecked (const File& file, int insertIndex, const bool shouldCompile)
  1287. {
  1288. Item item (project, ValueTree (Ids::FILE), belongsToModule);
  1289. item.initialiseMissingProperties();
  1290. item.getNameValue() = file.getFileName();
  1291. item.getShouldCompileValue() = shouldCompile && file.hasFileExtension (fileTypesToCompileByDefault);
  1292. item.getShouldAddToBinaryResourcesValue() = project.shouldBeAddedToBinaryResourcesByDefault (file);
  1293. if (canContain (item))
  1294. {
  1295. item.setFile (file);
  1296. addChild (item, insertIndex);
  1297. }
  1298. }
  1299. bool Project::Item::addRelativeFile (const RelativePath& file, int insertIndex, bool shouldCompile)
  1300. {
  1301. Item item (project, ValueTree (Ids::FILE), belongsToModule);
  1302. item.initialiseMissingProperties();
  1303. item.getNameValue() = file.getFileName();
  1304. item.getShouldCompileValue() = shouldCompile;
  1305. item.getShouldAddToBinaryResourcesValue() = project.shouldBeAddedToBinaryResourcesByDefault (file);
  1306. if (canContain (item))
  1307. {
  1308. item.setFile (file);
  1309. addChild (item, insertIndex);
  1310. return true;
  1311. }
  1312. return false;
  1313. }
  1314. Icon Project::Item::getIcon (bool isOpen) const
  1315. {
  1316. auto& icons = getIcons();
  1317. if (isFile())
  1318. {
  1319. if (isImageFile())
  1320. return Icon (icons.imageDoc, Colours::transparentBlack);
  1321. return { icons.file, Colours::transparentBlack };
  1322. }
  1323. if (isMainGroup())
  1324. return { icons.juceLogo, Colours::orange };
  1325. return { isOpen ? icons.openFolder : icons.closedFolder, Colours::transparentBlack };
  1326. }
  1327. bool Project::Item::isIconCrossedOut() const
  1328. {
  1329. return isFile()
  1330. && ! (shouldBeCompiled()
  1331. || shouldBeAddedToBinaryResources()
  1332. || getFile().hasFileExtension (headerFileExtensions));
  1333. }
  1334. bool Project::Item::needsSaving() const noexcept
  1335. {
  1336. auto& odm = ProjucerApplication::getApp().openDocumentManager;
  1337. if (odm.anyFilesNeedSaving())
  1338. {
  1339. for (int i = 0; i < odm.getNumOpenDocuments(); ++i)
  1340. {
  1341. auto* doc = odm.getOpenDocument (i);
  1342. if (doc->needsSaving() && doc->getFile() == getFile())
  1343. return true;
  1344. }
  1345. }
  1346. return false;
  1347. }
  1348. //==============================================================================
  1349. ValueTree Project::getConfigNode()
  1350. {
  1351. return projectRoot.getOrCreateChildWithName (Ids::JUCEOPTIONS, nullptr);
  1352. }
  1353. ValueWithDefault Project::getConfigFlag (const String& name)
  1354. {
  1355. auto configNode = getConfigNode();
  1356. return { configNode, name, getUndoManagerFor (configNode) };
  1357. }
  1358. bool Project::isConfigFlagEnabled (const String& name, bool defaultIsEnabled) const
  1359. {
  1360. auto configValue = projectRoot.getChildWithName (Ids::JUCEOPTIONS).getProperty (name, "default");
  1361. if (configValue == "default")
  1362. return defaultIsEnabled;
  1363. return configValue;
  1364. }
  1365. //==============================================================================
  1366. StringArray Project::getCompilerFlagSchemes() const
  1367. {
  1368. if (compilerFlagSchemesValue.isUsingDefault())
  1369. return {};
  1370. StringArray schemes;
  1371. auto schemesVar = compilerFlagSchemesValue.get();
  1372. if (auto* arr = schemesVar.getArray())
  1373. schemes.addArray (arr->begin(), arr->end());
  1374. return schemes;
  1375. }
  1376. void Project::addCompilerFlagScheme (const String& schemeToAdd)
  1377. {
  1378. auto schemesVar = compilerFlagSchemesValue.get();
  1379. if (auto* arr = schemesVar.getArray())
  1380. {
  1381. arr->addIfNotAlreadyThere (schemeToAdd);
  1382. compilerFlagSchemesValue.setValue ({ *arr }, getUndoManager());
  1383. }
  1384. }
  1385. void Project::removeCompilerFlagScheme (const String& schemeToRemove)
  1386. {
  1387. auto schemesVar = compilerFlagSchemesValue.get();
  1388. if (auto* arr = schemesVar.getArray())
  1389. {
  1390. for (int i = 0; i < arr->size(); ++i)
  1391. {
  1392. if (arr->getUnchecked (i).toString() == schemeToRemove)
  1393. {
  1394. arr->remove (i);
  1395. if (arr->isEmpty())
  1396. compilerFlagSchemesValue.resetToDefault();
  1397. else
  1398. compilerFlagSchemesValue.setValue ({ *arr }, getUndoManager());
  1399. return;
  1400. }
  1401. }
  1402. }
  1403. }
  1404. //==============================================================================
  1405. static String getCompanyNameOrDefault (StringRef str)
  1406. {
  1407. if (str.isEmpty())
  1408. return "yourcompany";
  1409. return str;
  1410. }
  1411. String Project::getDefaultBundleIdentifierString() const
  1412. {
  1413. return "com." + CodeHelpers::makeValidIdentifier (getCompanyNameOrDefault (getCompanyNameString()), false, true, false)
  1414. + "." + CodeHelpers::makeValidIdentifier (getProjectNameString(), false, true, false);
  1415. }
  1416. String Project::getDefaultPluginManufacturerString() const
  1417. {
  1418. return getCompanyNameOrDefault (getCompanyNameString());
  1419. }
  1420. String Project::getAUMainTypeString() const noexcept
  1421. {
  1422. auto v = pluginAUMainTypeValue.get();
  1423. if (auto* arr = v.getArray())
  1424. return arr->getFirst().toString();
  1425. jassertfalse;
  1426. return {};
  1427. }
  1428. bool Project::isAUSandBoxSafe() const noexcept
  1429. {
  1430. return pluginAUSandboxSafeValue.get();
  1431. }
  1432. String Project::getVSTCategoryString() const noexcept
  1433. {
  1434. auto v = pluginVSTCategoryValue.get();
  1435. if (auto* arr = v.getArray())
  1436. return arr->getFirst().toString();
  1437. jassertfalse;
  1438. return {};
  1439. }
  1440. static String getVST3CategoryStringFromSelection (Array<var> selected, const Project& p) noexcept
  1441. {
  1442. StringArray categories;
  1443. for (auto& category : selected)
  1444. categories.add (category);
  1445. // One of these needs to be selected in order for the plug-in to be recognised in Cubase
  1446. if (! categories.contains ("Fx") && ! categories.contains ("Instrument"))
  1447. {
  1448. categories.insert (0, p.isPluginSynth() ? "Instrument"
  1449. : "Fx");
  1450. }
  1451. else
  1452. {
  1453. // "Fx" and "Instrument" should come first and if both are present prioritise "Fx"
  1454. if (categories.contains ("Instrument"))
  1455. categories.move (categories.indexOf ("Instrument"), 0);
  1456. if (categories.contains ("Fx"))
  1457. categories.move (categories.indexOf ("Fx"), 0);
  1458. }
  1459. return categories.joinIntoString ("|");
  1460. }
  1461. String Project::getVST3CategoryString() const noexcept
  1462. {
  1463. auto v = pluginVST3CategoryValue.get();
  1464. if (auto* arr = v.getArray())
  1465. return getVST3CategoryStringFromSelection (*arr, *this);
  1466. jassertfalse;
  1467. return {};
  1468. }
  1469. int Project::getAAXCategory() const noexcept
  1470. {
  1471. int res = 0;
  1472. auto v = pluginAAXCategoryValue.get();
  1473. if (auto* arr = v.getArray())
  1474. {
  1475. for (auto c : *arr)
  1476. res |= static_cast<int> (c);
  1477. }
  1478. return res;
  1479. }
  1480. int Project::getRTASCategory() const noexcept
  1481. {
  1482. int res = 0;
  1483. auto v = pluginRTASCategoryValue.get();
  1484. if (auto* arr = v.getArray())
  1485. {
  1486. for (auto c : *arr)
  1487. res |= static_cast<int> (c);
  1488. }
  1489. return res;
  1490. }
  1491. String Project::getIAATypeCode()
  1492. {
  1493. String s;
  1494. if (pluginWantsMidiInput())
  1495. {
  1496. if (isPluginSynth())
  1497. s = "auri";
  1498. else
  1499. s = "aurm";
  1500. }
  1501. else
  1502. {
  1503. if (isPluginSynth())
  1504. s = "aurg";
  1505. else
  1506. s = "aurx";
  1507. }
  1508. return s;
  1509. }
  1510. String Project::getIAAPluginName()
  1511. {
  1512. auto s = getPluginManufacturerString();
  1513. s << ": ";
  1514. s << getPluginNameString();
  1515. return s;
  1516. }
  1517. //==============================================================================
  1518. bool Project::isAUPluginHost()
  1519. {
  1520. return getEnabledModules().isModuleEnabled ("juce_audio_processors") && isConfigFlagEnabled ("JUCE_PLUGINHOST_AU", false);
  1521. }
  1522. bool Project::isVSTPluginHost()
  1523. {
  1524. return getEnabledModules().isModuleEnabled ("juce_audio_processors") && isConfigFlagEnabled ("JUCE_PLUGINHOST_VST", false);
  1525. }
  1526. bool Project::isVST3PluginHost()
  1527. {
  1528. return getEnabledModules().isModuleEnabled ("juce_audio_processors") && isConfigFlagEnabled ("JUCE_PLUGINHOST_VST3", false);
  1529. }
  1530. //==============================================================================
  1531. StringArray Project::getAllAUMainTypeStrings() noexcept
  1532. {
  1533. static StringArray auMainTypeStrings { "kAudioUnitType_Effect", "kAudioUnitType_FormatConverter", "kAudioUnitType_Generator", "kAudioUnitType_MIDIProcessor",
  1534. "kAudioUnitType_Mixer", "kAudioUnitType_MusicDevice", "kAudioUnitType_MusicEffect", "kAudioUnitType_OfflineEffect",
  1535. "kAudioUnitType_Output", "kAudioUnitType_Panner" };
  1536. return auMainTypeStrings;
  1537. }
  1538. Array<var> Project::getAllAUMainTypeVars() noexcept
  1539. {
  1540. static Array<var> auMainTypeVars { "'aufx'", "'aufc'", "'augn'", "'aumi'",
  1541. "'aumx'", "'aumu'", "'aumf'", "'auol'",
  1542. "'auou'", "'aupn'" };
  1543. return auMainTypeVars;
  1544. }
  1545. Array<var> Project::getDefaultAUMainTypes() const noexcept
  1546. {
  1547. if (isPluginMidiEffect()) return { "'aumi'" };
  1548. if (isPluginSynth()) return { "'aumu'" };
  1549. if (pluginWantsMidiInput()) return { "'aumf'" };
  1550. return { "'aufx'" };
  1551. }
  1552. StringArray Project::getAllVSTCategoryStrings() noexcept
  1553. {
  1554. static StringArray vstCategoryStrings { "kPlugCategUnknown", "kPlugCategEffect", "kPlugCategSynth", "kPlugCategAnalysis", "kPlugCategMastering",
  1555. "kPlugCategSpacializer", "kPlugCategRoomFx", "kPlugSurroundFx", "kPlugCategRestoration", "kPlugCategOfflineProcess",
  1556. "kPlugCategShell", "kPlugCategGenerator" };
  1557. return vstCategoryStrings;
  1558. }
  1559. Array<var> Project::getDefaultVSTCategories() const noexcept
  1560. {
  1561. if (isPluginSynth())
  1562. return { "kPlugCategSynth" };
  1563. return { "kPlugCategEffect" };
  1564. }
  1565. StringArray Project::getAllVST3CategoryStrings() noexcept
  1566. {
  1567. static StringArray vst3CategoryStrings { "Fx", "Instrument", "Analyzer", "Delay", "Distortion", "Drum", "Dynamics", "EQ", "External", "Filter",
  1568. "Generator", "Mastering", "Modulation", "Mono", "Network", "NoOfflineProcess", "OnlyOfflineProcess", "OnlyRT",
  1569. "Pitch Shift", "Restoration", "Reverb", "Sampler", "Spatial", "Stereo", "Surround", "Synth", "Tools", "Up-Downmix" };
  1570. return vst3CategoryStrings;
  1571. }
  1572. Array<var> Project::getDefaultVST3Categories() const noexcept
  1573. {
  1574. if (isPluginSynth())
  1575. return { "Instrument", "Synth" };
  1576. return { "Fx" };
  1577. }
  1578. StringArray Project::getAllAAXCategoryStrings() noexcept
  1579. {
  1580. static StringArray aaxCategoryStrings { "AAX_ePlugInCategory_None", "AAX_ePlugInCategory_EQ", "AAX_ePlugInCategory_Dynamics", "AAX_ePlugInCategory_PitchShift",
  1581. "AAX_ePlugInCategory_Reverb", "AAX_ePlugInCategory_Delay", "AAX_ePlugInCategory_Modulation", "AAX_ePlugInCategory_Harmonic",
  1582. "AAX_ePlugInCategory_NoiseReduction", "AAX_ePlugInCategory_Dither", "AAX_ePlugInCategory_SoundField", "AAX_ePlugInCategory_HWGenerators",
  1583. "AAX_ePlugInCategory_SWGenerators", "AAX_ePlugInCategory_WrappedPlugin", "AAX_EPlugInCategory_Effect" };
  1584. return aaxCategoryStrings;
  1585. }
  1586. Array<var> Project::getAllAAXCategoryVars() noexcept
  1587. {
  1588. static Array<var> aaxCategoryVars { 0x00000000, 0x00000001, 0x00000002, 0x00000004,
  1589. 0x00000008, 0x00000010, 0x00000020, 0x00000040,
  1590. 0x00000080, 0x00000100, 0x00000200, 0x00000400,
  1591. 0x00000800, 0x00001000, 0x00002000 };
  1592. return aaxCategoryVars;
  1593. }
  1594. Array<var> Project::getDefaultAAXCategories() const noexcept
  1595. {
  1596. if (isPluginSynth())
  1597. return getAllAAXCategoryVars()[getAllAAXCategoryStrings().indexOf ("AAX_ePlugInCategory_SWGenerators")];
  1598. return getAllAAXCategoryVars()[getAllAAXCategoryStrings().indexOf ("AAX_ePlugInCategory_None")];
  1599. }
  1600. StringArray Project::getAllRTASCategoryStrings() noexcept
  1601. {
  1602. static StringArray rtasCategoryStrings { "ePlugInCategory_None", "ePlugInCategory_EQ", "ePlugInCategory_Dynamics", "ePlugInCategory_PitchShift",
  1603. "ePlugInCategory_Reverb", "ePlugInCategory_Delay", "ePlugInCategory_Modulation", "ePlugInCategory_Harmonic",
  1604. "ePlugInCategory_NoiseReduction", "ePlugInCategory_Dither", "ePlugInCategory_SoundField", "ePlugInCategory_HWGenerators",
  1605. "ePlugInCategory_SWGenerators", "ePlugInCategory_WrappedPlugin", "ePlugInCategory_Effect" };
  1606. return rtasCategoryStrings;
  1607. }
  1608. Array<var> Project::getAllRTASCategoryVars() noexcept
  1609. {
  1610. static Array<var> rtasCategoryVars { 0x00000000, 0x00000001, 0x00000002, 0x00000004,
  1611. 0x00000008, 0x00000010, 0x00000020, 0x00000040,
  1612. 0x00000080, 0x00000100, 0x00000200, 0x00000400,
  1613. 0x00000800, 0x00001000, 0x00002000 };
  1614. return rtasCategoryVars;
  1615. }
  1616. Array<var> Project::getDefaultRTASCategories() const noexcept
  1617. {
  1618. if (isPluginSynth())
  1619. return getAllRTASCategoryVars()[getAllRTASCategoryStrings().indexOf ("ePlugInCategory_SWGenerators")];
  1620. return getAllRTASCategoryVars()[getAllRTASCategoryStrings().indexOf ("ePlugInCategory_None")];
  1621. }
  1622. //==============================================================================
  1623. EnabledModuleList& Project::getEnabledModules()
  1624. {
  1625. if (enabledModuleList == nullptr)
  1626. enabledModuleList.reset (new EnabledModuleList (*this, projectRoot.getOrCreateChildWithName (Ids::MODULES, nullptr)));
  1627. return *enabledModuleList;
  1628. }
  1629. static StringArray getModulePathsFromExporters (Project& project, bool onlyThisOS)
  1630. {
  1631. StringArray paths;
  1632. for (Project::ExporterIterator exporter (project); exporter.next();)
  1633. {
  1634. if (onlyThisOS && ! exporter->mayCompileOnCurrentOS())
  1635. continue;
  1636. auto& modules = project.getEnabledModules();
  1637. auto n = modules.getNumModules();
  1638. for (int i = 0; i < n; ++i)
  1639. {
  1640. auto id = modules.getModuleID (i);
  1641. if (modules.shouldUseGlobalPath (id))
  1642. continue;
  1643. auto path = exporter->getPathForModuleString (id);
  1644. if (path.isNotEmpty())
  1645. paths.addIfNotAlreadyThere (path);
  1646. }
  1647. auto oldPath = exporter->getLegacyModulePath();
  1648. if (oldPath.isNotEmpty())
  1649. paths.addIfNotAlreadyThere (oldPath);
  1650. }
  1651. return paths;
  1652. }
  1653. static Array<File> getExporterModulePathsToScan (Project& project)
  1654. {
  1655. auto exporterPaths = getModulePathsFromExporters (project, true);
  1656. if (exporterPaths.isEmpty())
  1657. exporterPaths = getModulePathsFromExporters (project, false);
  1658. Array<File> files;
  1659. for (auto& path : exporterPaths)
  1660. {
  1661. auto f = project.resolveFilename (path);
  1662. if (f.isDirectory())
  1663. {
  1664. files.addIfNotAlreadyThere (f);
  1665. if (f.getChildFile ("modules").isDirectory())
  1666. files.addIfNotAlreadyThere (f.getChildFile ("modules"));
  1667. }
  1668. }
  1669. return files;
  1670. }
  1671. AvailableModuleList& Project::getExporterPathsModuleList()
  1672. {
  1673. return *exporterPathsModuleList;
  1674. }
  1675. void Project::rescanExporterPathModules (bool async)
  1676. {
  1677. if (async)
  1678. exporterPathsModuleList->scanPathsAsync (getExporterModulePathsToScan (*this));
  1679. else
  1680. exporterPathsModuleList->scanPaths (getExporterModulePathsToScan (*this));
  1681. }
  1682. AvailableModuleList::ModuleIDAndFolder Project::getModuleWithID (const String& id)
  1683. {
  1684. if (! getEnabledModules().shouldUseGlobalPath (id))
  1685. {
  1686. const auto& mod = exporterPathsModuleList->getModuleWithID (id);
  1687. if (mod.second != File())
  1688. return mod;
  1689. }
  1690. const auto& list = (isJUCEModule (id) ? ProjucerApplication::getApp().getJUCEPathModuleList().getAllModules()
  1691. : ProjucerApplication::getApp().getUserPathsModuleList().getAllModules());
  1692. for (auto& m : list)
  1693. if (m.first == id)
  1694. return m;
  1695. return exporterPathsModuleList->getModuleWithID (id);
  1696. }
  1697. //==============================================================================
  1698. ValueTree Project::getExporters()
  1699. {
  1700. return projectRoot.getOrCreateChildWithName (Ids::EXPORTFORMATS, nullptr);
  1701. }
  1702. int Project::getNumExporters()
  1703. {
  1704. return getExporters().getNumChildren();
  1705. }
  1706. ProjectExporter* Project::createExporter (int index)
  1707. {
  1708. jassert (index >= 0 && index < getNumExporters());
  1709. return ProjectExporter::createExporter (*this, getExporters().getChild (index));
  1710. }
  1711. void Project::addNewExporter (const String& exporterName)
  1712. {
  1713. std::unique_ptr<ProjectExporter> exp (ProjectExporter::createNewExporter (*this, exporterName));
  1714. exp->getTargetLocationValue() = exp->getTargetLocationString()
  1715. + getUniqueTargetFolderSuffixForExporter (exp->getName(), exp->getTargetLocationString());
  1716. auto exportersTree = getExporters();
  1717. exportersTree.appendChild (exp->settings, getUndoManagerFor (exportersTree));
  1718. }
  1719. void Project::createExporterForCurrentPlatform()
  1720. {
  1721. addNewExporter (ProjectExporter::getCurrentPlatformExporterName());
  1722. }
  1723. String Project::getUniqueTargetFolderSuffixForExporter (const String& exporterName, const String& base)
  1724. {
  1725. StringArray buildFolders;
  1726. auto exportersTree = getExporters();
  1727. auto type = ProjectExporter::getValueTreeNameForExporter (exporterName);
  1728. for (int i = 0; i < exportersTree.getNumChildren(); ++i)
  1729. {
  1730. auto exporterNode = exportersTree.getChild (i);
  1731. if (exporterNode.getType() == Identifier (type))
  1732. buildFolders.add (exporterNode.getProperty ("targetFolder").toString());
  1733. }
  1734. if (buildFolders.size() == 0 || ! buildFolders.contains (base))
  1735. return {};
  1736. buildFolders.remove (buildFolders.indexOf (base));
  1737. int num = 1;
  1738. for (auto f : buildFolders)
  1739. {
  1740. if (! f.endsWith ("_" + String (num)))
  1741. break;
  1742. ++num;
  1743. }
  1744. return "_" + String (num);
  1745. }
  1746. //==============================================================================
  1747. bool Project::shouldSendGUIBuilderAnalyticsEvent() noexcept
  1748. {
  1749. if (! hasSentGUIBuilderAnalyticsEvent)
  1750. {
  1751. hasSentGUIBuilderAnalyticsEvent = true;
  1752. return true;
  1753. }
  1754. return false;
  1755. }
  1756. //==============================================================================
  1757. String Project::getFileTemplate (const String& templateName)
  1758. {
  1759. int dataSize;
  1760. if (auto* data = BinaryData::getNamedResource (templateName.toUTF8(), dataSize))
  1761. return String::fromUTF8 (data, dataSize);
  1762. jassertfalse;
  1763. return {};
  1764. }
  1765. //==============================================================================
  1766. Project::ExporterIterator::ExporterIterator (Project& p) : index (-1), project (p) {}
  1767. Project::ExporterIterator::~ExporterIterator() {}
  1768. bool Project::ExporterIterator::next()
  1769. {
  1770. if (++index >= project.getNumExporters())
  1771. return false;
  1772. exporter.reset (project.createExporter (index));
  1773. if (exporter == nullptr)
  1774. {
  1775. jassertfalse; // corrupted project file?
  1776. return next();
  1777. }
  1778. return true;
  1779. }