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.

1453 lines
52KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. #include "../jucer_Headers.h"
  20. #include "jucer_Project.h"
  21. #include "../Project Saving/jucer_ProjectExporter.h"
  22. #include "../Project Saving/jucer_ProjectSaver.h"
  23. #include "../Application/jucer_OpenDocumentManager.h"
  24. #include "../Application/jucer_Application.h"
  25. namespace
  26. {
  27. String makeValid4CC (const String& seed)
  28. {
  29. String s (CodeHelpers::makeValidIdentifier (seed, false, true, false) + "xxxx");
  30. return s.substring (0, 1).toUpperCase()
  31. + s.substring (1, 4).toLowerCase();
  32. }
  33. }
  34. //==============================================================================
  35. Project::Project (const File& f)
  36. : FileBasedDocument (projectFileExtension,
  37. String ("*") + projectFileExtension,
  38. "Choose a Jucer project to load",
  39. "Save Jucer project"),
  40. projectRoot (Ids::JUCERPROJECT),
  41. isSaving (false)
  42. {
  43. Logger::writeToLog ("Loading project: " + f.getFullPathName());
  44. setFile (f);
  45. removeDefunctExporters();
  46. updateOldModulePaths();
  47. setMissingDefaultValues();
  48. setChangedFlag (false);
  49. projectRoot.addListener (this);
  50. modificationTime = getFile().getLastModificationTime();
  51. }
  52. Project::~Project()
  53. {
  54. projectRoot.removeListener (this);
  55. ProjucerApplication::getApp().openDocumentManager.closeAllDocumentsUsingProject (*this, false);
  56. }
  57. const char* Project::projectFileExtension = ".jucer";
  58. //==============================================================================
  59. void Project::setTitle (const String& newTitle)
  60. {
  61. projectRoot.setProperty (Ids::name, newTitle, getUndoManagerFor (projectRoot));
  62. getMainGroup().getNameValue() = newTitle;
  63. }
  64. String Project::getTitle() const
  65. {
  66. return projectRoot.getChildWithName (Ids::MAINGROUP) [Ids::name];
  67. }
  68. String Project::getDocumentTitle()
  69. {
  70. return getTitle();
  71. }
  72. void Project::updateProjectSettings()
  73. {
  74. projectRoot.setProperty (Ids::jucerVersion, ProjectInfo::versionString, nullptr);
  75. projectRoot.setProperty (Ids::name, getDocumentTitle(), nullptr);
  76. }
  77. void Project::setMissingDefaultValues()
  78. {
  79. if (! projectRoot.hasProperty (Ids::ID))
  80. projectRoot.setProperty (Ids::ID, createAlphaNumericUID(), nullptr);
  81. // Create main file group if missing
  82. if (! projectRoot.getChildWithName (Ids::MAINGROUP).isValid())
  83. {
  84. Item mainGroup (*this, ValueTree (Ids::MAINGROUP), false);
  85. projectRoot.addChild (mainGroup.state, 0, 0);
  86. }
  87. getMainGroup().initialiseMissingProperties();
  88. if (getDocumentTitle().isEmpty())
  89. setTitle ("JUCE Project");
  90. {
  91. auto defaultSplashScreenAndReporting = ! ProjucerApplication::getApp().isPaidOrGPL();
  92. if (shouldDisplaySplashScreen() == var() || defaultSplashScreenAndReporting)
  93. shouldDisplaySplashScreen() = defaultSplashScreenAndReporting;
  94. if (shouldReportAppUsage() == var() || defaultSplashScreenAndReporting)
  95. shouldReportAppUsage() = defaultSplashScreenAndReporting;
  96. }
  97. if (splashScreenColour() == var())
  98. splashScreenColour() = "Dark";
  99. if (! projectRoot.hasProperty (Ids::projectType))
  100. getProjectTypeValue() = ProjectType_GUIApp::getTypeName();
  101. if (! projectRoot.hasProperty (Ids::version))
  102. getVersionValue() = "1.0.0";
  103. updateOldStyleConfigList();
  104. moveOldPropertyFromProjectToAllExporters (Ids::bigIcon);
  105. moveOldPropertyFromProjectToAllExporters (Ids::smallIcon);
  106. if (getProjectType().isAudioPlugin())
  107. setMissingAudioPluginDefaultValues();
  108. getModules().sortAlphabetically();
  109. if (getBundleIdentifier().toString().isEmpty())
  110. getBundleIdentifier() = getDefaultBundleIdentifier();
  111. if (shouldIncludeBinaryInAppConfig() == var())
  112. shouldIncludeBinaryInAppConfig() = true;
  113. ProjucerApplication::getApp().updateNewlyOpenedProject (*this);
  114. }
  115. void Project::updateDeprecatedProjectSettingsInteractively()
  116. {
  117. jassert (! ProjucerApplication::getApp().isRunningCommandLine);
  118. for (Project::ExporterIterator exporter (*this); exporter.next();)
  119. exporter->updateDeprecatedProjectSettingsInteractively();
  120. }
  121. void Project::setMissingAudioPluginDefaultValues()
  122. {
  123. const String sanitisedProjectName (CodeHelpers::makeValidIdentifier (getTitle(), false, true, false));
  124. setValueIfVoid (getShouldBuildVSTAsValue(), true);
  125. setValueIfVoid (getShouldBuildVST3AsValue(), false);
  126. setValueIfVoid (getShouldBuildAUAsValue(), true);
  127. setValueIfVoid (getShouldBuildAUv3AsValue(), false);
  128. setValueIfVoid (getShouldBuildRTASAsValue(), false);
  129. setValueIfVoid (getShouldBuildAAXAsValue(), false);
  130. setValueIfVoid (getShouldBuildStandalonePluginAsValue(), false);
  131. setValueIfVoid (getShouldEnableIAAAsValue(), false);
  132. setValueIfVoid (getPluginName(), getTitle());
  133. setValueIfVoid (getPluginDesc(), getTitle());
  134. setValueIfVoid (getPluginManufacturer(), "yourcompany");
  135. setValueIfVoid (getPluginManufacturerCode(), "Manu");
  136. setValueIfVoid (getPluginCode(), makeValid4CC (getProjectUID() + getProjectUID()));
  137. setValueIfVoid (getPluginChannelConfigs(), String());
  138. setValueIfVoid (getPluginIsSynth(), false);
  139. setValueIfVoid (getPluginWantsMidiInput(), false);
  140. setValueIfVoid (getPluginProducesMidiOut(), false);
  141. setValueIfVoid (getPluginIsMidiEffectPlugin(), false);
  142. setValueIfVoid (getPluginEditorNeedsKeyFocus(), false);
  143. setValueIfVoid (getPluginAUExportPrefix(), sanitisedProjectName + "AU");
  144. setValueIfVoid (getPluginRTASCategory(), String());
  145. setValueIfVoid (getBundleIdentifier(), getDefaultBundleIdentifier());
  146. setValueIfVoid (getAAXIdentifier(), getDefaultAAXIdentifier());
  147. setValueIfVoid (getPluginAAXCategory(), "AAX_ePlugInCategory_Dynamics");
  148. }
  149. void Project::updateOldStyleConfigList()
  150. {
  151. ValueTree deprecatedConfigsList (projectRoot.getChildWithName (Ids::CONFIGURATIONS));
  152. if (deprecatedConfigsList.isValid())
  153. {
  154. projectRoot.removeChild (deprecatedConfigsList, nullptr);
  155. for (Project::ExporterIterator exporter (*this); exporter.next();)
  156. {
  157. if (exporter->getNumConfigurations() == 0)
  158. {
  159. ValueTree newConfigs (deprecatedConfigsList.createCopy());
  160. if (! exporter->isXcode())
  161. {
  162. for (int j = newConfigs.getNumChildren(); --j >= 0;)
  163. {
  164. ValueTree config (newConfigs.getChild(j));
  165. config.removeProperty (Ids::osxSDK, nullptr);
  166. config.removeProperty (Ids::osxCompatibility, nullptr);
  167. config.removeProperty (Ids::osxArchitecture, nullptr);
  168. }
  169. }
  170. exporter->settings.addChild (newConfigs, 0, nullptr);
  171. }
  172. }
  173. }
  174. }
  175. void Project::moveOldPropertyFromProjectToAllExporters (Identifier name)
  176. {
  177. if (projectRoot.hasProperty (name))
  178. {
  179. for (Project::ExporterIterator exporter (*this); exporter.next();)
  180. exporter->settings.setProperty (name, projectRoot [name], nullptr);
  181. projectRoot.removeProperty (name, nullptr);
  182. }
  183. }
  184. void Project::removeDefunctExporters()
  185. {
  186. ValueTree exporters (projectRoot.getChildWithName (Ids::EXPORTFORMATS));
  187. StringPairArray oldExporters;
  188. oldExporters.set ("ANDROID", "Android Ant Exporter");
  189. oldExporters.set ("MSVC6", "MSVC6");
  190. oldExporters.set ("VS2010", "Visual Studio 2010");
  191. oldExporters.set ("VS2012", "Visual Studio 2012");
  192. for (auto& key : oldExporters.getAllKeys())
  193. {
  194. ValueTree oldExporter (exporters.getChildWithName (key));
  195. if (oldExporter.isValid())
  196. {
  197. AlertWindow::showMessageBox (AlertWindow::WarningIcon,
  198. TRANS (oldExporters[key]),
  199. TRANS ("The " + oldExporters[key] + " Exporter is deprecated. The exporter will be removed from this project."));
  200. exporters.removeChild (oldExporter, nullptr);
  201. }
  202. }
  203. }
  204. void Project::updateOldModulePaths()
  205. {
  206. for (Project::ExporterIterator exporter (*this); exporter.next();)
  207. exporter->updateOldModulePaths();
  208. }
  209. //==============================================================================
  210. static int getVersionElement (StringRef v, int index)
  211. {
  212. StringArray parts;
  213. parts.addTokens (v, "., ", StringRef());
  214. return parts [parts.size() - index - 1].getIntValue();
  215. }
  216. static int getJuceVersion (const String& v)
  217. {
  218. return getVersionElement (v, 2) * 100000
  219. + getVersionElement (v, 1) * 1000
  220. + getVersionElement (v, 0);
  221. }
  222. static int getBuiltJuceVersion()
  223. {
  224. return JUCE_MAJOR_VERSION * 100000
  225. + JUCE_MINOR_VERSION * 1000
  226. + JUCE_BUILDNUMBER;
  227. }
  228. static bool isAnyModuleNewerThanProjucer (const OwnedArray<ModuleDescription>& modules)
  229. {
  230. for (int i = modules.size(); --i >= 0;)
  231. {
  232. const ModuleDescription* m = modules.getUnchecked(i);
  233. if (m->getID().startsWith ("juce_")
  234. && getJuceVersion (m->getVersion()) > getBuiltJuceVersion())
  235. return true;
  236. }
  237. return false;
  238. }
  239. void Project::warnAboutOldProjucerVersion()
  240. {
  241. ModuleList available;
  242. available.scanAllKnownFolders (*this);
  243. if (isAnyModuleNewerThanProjucer (available.modules))
  244. {
  245. if (ProjucerApplication::getApp().isRunningCommandLine)
  246. std::cout << "WARNING! This version of the Projucer is out-of-date!" << std::endl;
  247. else
  248. AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
  249. "Projucer",
  250. "This version of the Projucer is out-of-date!"
  251. "\n\n"
  252. "Always make sure that you're running the very latest version, "
  253. "preferably compiled directly from the JUCE repository that you're working with!");
  254. }
  255. }
  256. //==============================================================================
  257. static File lastDocumentOpened;
  258. File Project::getLastDocumentOpened() { return lastDocumentOpened; }
  259. void Project::setLastDocumentOpened (const File& file) { lastDocumentOpened = file; }
  260. static void registerRecentFile (const File& file)
  261. {
  262. RecentlyOpenedFilesList::registerRecentFileNatively (file);
  263. getAppSettings().recentFiles.addFile (file);
  264. getAppSettings().flush();
  265. }
  266. //==============================================================================
  267. Result Project::loadDocument (const File& file)
  268. {
  269. ScopedPointer<XmlElement> xml (XmlDocument::parse (file));
  270. if (xml == nullptr || ! xml->hasTagName (Ids::JUCERPROJECT.toString()))
  271. return Result::fail ("Not a valid Jucer project!");
  272. ValueTree newTree (ValueTree::fromXml (*xml));
  273. if (! newTree.hasType (Ids::JUCERPROJECT))
  274. return Result::fail ("The document contains errors and couldn't be parsed!");
  275. registerRecentFile (file);
  276. enabledModulesList = nullptr;
  277. projectRoot = newTree;
  278. removeDefunctExporters();
  279. setMissingDefaultValues();
  280. updateOldModulePaths();
  281. setChangedFlag (false);
  282. if (! ProjucerApplication::getApp().isRunningCommandLine)
  283. warnAboutOldProjucerVersion();
  284. return Result::ok();
  285. }
  286. Result Project::saveDocument (const File& file)
  287. {
  288. return saveProject (file, false);
  289. }
  290. Result Project::saveProject (const File& file, bool isCommandLineApp)
  291. {
  292. if (isSaving)
  293. return Result::ok();
  294. updateProjectSettings();
  295. sanitiseConfigFlags();
  296. if (! isCommandLineApp)
  297. registerRecentFile (file);
  298. const ScopedValueSetter<bool> vs (isSaving, true, false);
  299. ProjectSaver saver (*this, file);
  300. return saver.save (! isCommandLineApp);
  301. }
  302. Result Project::saveResourcesOnly (const File& file)
  303. {
  304. ProjectSaver saver (*this, file);
  305. return saver.saveResourcesOnly();
  306. }
  307. //==============================================================================
  308. void Project::valueTreePropertyChanged (ValueTree&, const Identifier& property)
  309. {
  310. if (property == Ids::projectType)
  311. setMissingDefaultValues();
  312. changed();
  313. }
  314. void Project::valueTreeChildAdded (ValueTree&, ValueTree&) { changed(); }
  315. void Project::valueTreeChildRemoved (ValueTree&, ValueTree&, int) { changed(); }
  316. void Project::valueTreeChildOrderChanged (ValueTree&, int, int) { changed(); }
  317. void Project::valueTreeParentChanged (ValueTree&) {}
  318. //==============================================================================
  319. bool Project::hasProjectBeenModified()
  320. {
  321. Time newModificationTime = getFile().getLastModificationTime();
  322. Time oldModificationTime = modificationTime;
  323. modificationTime = newModificationTime;
  324. return (newModificationTime.toMilliseconds() > (oldModificationTime.toMilliseconds() + 1000LL));
  325. }
  326. //==============================================================================
  327. File Project::resolveFilename (String filename) const
  328. {
  329. if (filename.isEmpty())
  330. return {};
  331. filename = replacePreprocessorDefs (getPreprocessorDefs(), filename);
  332. if (FileHelpers::isAbsolutePath (filename))
  333. return File::createFileWithoutCheckingPath (FileHelpers::currentOSStylePath (filename)); // (avoid assertions for windows-style paths)
  334. return getFile().getSiblingFile (FileHelpers::currentOSStylePath (filename));
  335. }
  336. String Project::getRelativePathForFile (const File& file) const
  337. {
  338. String filename (file.getFullPathName());
  339. File relativePathBase (getFile().getParentDirectory());
  340. String p1 (relativePathBase.getFullPathName());
  341. String p2 (file.getFullPathName());
  342. while (p1.startsWithChar (File::separator))
  343. p1 = p1.substring (1);
  344. while (p2.startsWithChar (File::separator))
  345. p2 = p2.substring (1);
  346. if (p1.upToFirstOccurrenceOf (File::separatorString, true, false)
  347. .equalsIgnoreCase (p2.upToFirstOccurrenceOf (File::separatorString, true, false)))
  348. {
  349. filename = FileHelpers::getRelativePathFrom (file, relativePathBase);
  350. }
  351. return filename;
  352. }
  353. //==============================================================================
  354. const ProjectType& Project::getProjectType() const
  355. {
  356. if (const ProjectType* type = ProjectType::findType (getProjectTypeString()))
  357. return *type;
  358. const ProjectType* guiType = ProjectType::findType (ProjectType_GUIApp::getTypeName());
  359. jassert (guiType != nullptr);
  360. return *guiType;
  361. }
  362. bool Project::shouldBuildTargetType (ProjectType::Target::Type targetType) const noexcept
  363. {
  364. const ProjectType& projectType = getProjectType();
  365. if (! projectType.supportsTargetType (targetType))
  366. return false;
  367. switch (targetType)
  368. {
  369. case ProjectType::Target::VSTPlugIn:
  370. return shouldBuildVST();
  371. case ProjectType::Target::VST3PlugIn:
  372. return shouldBuildVST3();
  373. case ProjectType::Target::AAXPlugIn:
  374. return shouldBuildAAX();
  375. case ProjectType::Target::RTASPlugIn:
  376. return shouldBuildRTAS();
  377. case ProjectType::Target::AudioUnitPlugIn:
  378. return shouldBuildAU();
  379. case ProjectType::Target::AudioUnitv3PlugIn:
  380. return shouldBuildAUv3();
  381. case ProjectType::Target::StandalonePlugIn:
  382. return shouldBuildStandalonePlugin();
  383. case ProjectType::Target::AggregateTarget:
  384. case ProjectType::Target::SharedCodeTarget:
  385. return projectType.isAudioPlugin();
  386. case ProjectType::Target::unspecified:
  387. return false;
  388. default:
  389. break;
  390. }
  391. return true;
  392. }
  393. ProjectType::Target::Type Project::getTargetTypeFromFilePath (const File& file, bool returnSharedTargetIfNoValidSuffix)
  394. {
  395. if (LibraryModule::CompileUnit::hasSuffix (file, "_AU")) return ProjectType::Target::AudioUnitPlugIn;
  396. else if (LibraryModule::CompileUnit::hasSuffix (file, "_AUv3")) return ProjectType::Target::AudioUnitv3PlugIn;
  397. else if (LibraryModule::CompileUnit::hasSuffix (file, "_AAX")) return ProjectType::Target::AAXPlugIn;
  398. else if (LibraryModule::CompileUnit::hasSuffix (file, "_RTAS")) return ProjectType::Target::RTASPlugIn;
  399. else if (LibraryModule::CompileUnit::hasSuffix (file, "_VST2")) return ProjectType::Target::VSTPlugIn;
  400. else if (LibraryModule::CompileUnit::hasSuffix (file, "_VST3")) return ProjectType::Target::VST3PlugIn;
  401. else if (LibraryModule::CompileUnit::hasSuffix (file, "_Standalone")) return ProjectType::Target::StandalonePlugIn;
  402. return (returnSharedTargetIfNoValidSuffix ? ProjectType::Target::SharedCodeTarget : ProjectType::Target::unspecified);
  403. }
  404. const char* ProjectType::Target::getName() const noexcept
  405. {
  406. switch (type)
  407. {
  408. case GUIApp: return "App";
  409. case ConsoleApp: return "ConsoleApp";
  410. case StaticLibrary: return "Static Library";
  411. case DynamicLibrary: return "Dynamic Library";
  412. case VSTPlugIn: return "VST";
  413. case VST3PlugIn: return "VST3";
  414. case AudioUnitPlugIn: return "AU";
  415. case StandalonePlugIn: return "Standalone Plugin";
  416. case AudioUnitv3PlugIn: return "AUv3 AppExtension";
  417. case AAXPlugIn: return "AAX";
  418. case RTASPlugIn: return "RTAS";
  419. case SharedCodeTarget: return "Shared Code";
  420. case AggregateTarget: return "All";
  421. default: return "undefined";
  422. }
  423. }
  424. ProjectType::Target::TargetFileType ProjectType::Target::getTargetFileType() const noexcept
  425. {
  426. switch (type)
  427. {
  428. case GUIApp: return executable;
  429. case ConsoleApp: return executable;
  430. case StaticLibrary: return staticLibrary;
  431. case DynamicLibrary: return sharedLibraryOrDLL;
  432. case VSTPlugIn: return pluginBundle;
  433. case VST3PlugIn: return pluginBundle;
  434. case AudioUnitPlugIn: return pluginBundle;
  435. case StandalonePlugIn: return executable;
  436. case AudioUnitv3PlugIn: return macOSAppex;
  437. case AAXPlugIn: return pluginBundle;
  438. case RTASPlugIn: return pluginBundle;
  439. case SharedCodeTarget: return staticLibrary;
  440. default:
  441. break;
  442. }
  443. return unknown;
  444. }
  445. //==============================================================================
  446. void Project::createPropertyEditors (PropertyListBuilder& props)
  447. {
  448. props.add (new TextPropertyComponent (getProjectNameValue(), "Project Name", 256, false),
  449. "The name of the project.");
  450. props.add (new TextPropertyComponent (getVersionValue(), "Project Version", 16, false),
  451. "The project's version number, This should be in the format major.minor.point[.point]");
  452. props.add (new TextPropertyComponent (getCompanyName(), "Company Name", 256, false),
  453. "Your company name, which will be added to the properties of the binary where possible");
  454. props.add (new TextPropertyComponent (getCompanyWebsite(), "Company Website", 256, false),
  455. "Your company website, which will be added to the properties of the binary where possible");
  456. props.add (new TextPropertyComponent (getCompanyEmail(), "Company E-mail", 256, false),
  457. "Your company e-mail, which will be added to the properties of the binary where possible");
  458. {
  459. const String licenseRequiredTagline ("Required for closed source applications without an Indie or Pro JUCE license");
  460. const String licenseRequiredInfo ("In accordance with the terms of the JUCE 5 End-Use License Agreement (www.juce.com/juce-5-licence), "
  461. "this option can only be disabled for closed source applications if you have a JUCE Indie or Pro "
  462. "license, or are using JUCE under the GPL v3 license.");
  463. StringPairArray description;
  464. description.set ("Report JUCE app usage", "This option controls the collection of usage data from users of this JUCE application.");
  465. description.set ("Display the JUCE splash screen", "This option controls the display of the standard JUCE splash screen.");
  466. if (ProjucerApplication::getApp().isPaidOrGPL())
  467. {
  468. props.add (new BooleanPropertyComponent (shouldReportAppUsage(), "Report JUCE app usage", licenseRequiredTagline),
  469. description["Report JUCE app usage"] + " " + licenseRequiredInfo);
  470. props.add (new BooleanPropertyComponent (shouldDisplaySplashScreen(), "Display the JUCE splash screen", licenseRequiredTagline),
  471. description["Display the JUCE splash screen"] + " " + licenseRequiredInfo);
  472. }
  473. else
  474. {
  475. StringArray options;
  476. Array<var> vars;
  477. options.add (licenseRequiredTagline);
  478. vars.add (var());
  479. props.add (new ChoicePropertyComponent (Value(), "Report JUCE app usage", options, vars),
  480. description["Report JUCE app usage"] + " " + licenseRequiredInfo);
  481. props.add (new ChoicePropertyComponent (Value(), "Display the JUCE splash screen", options, vars),
  482. description["Display the JUCE splash screen"] + " " + licenseRequiredInfo);
  483. }
  484. }
  485. {
  486. StringArray splashScreenColours;
  487. splashScreenColours.add ("Dark");
  488. splashScreenColours.add ("Light");
  489. Array<var> splashScreenCodes;
  490. for (auto& splashScreenColour : splashScreenColours)
  491. splashScreenCodes.add (splashScreenColour);
  492. props.add (new ChoicePropertyComponent (splashScreenColour(), "Splash screen colour", splashScreenColours, splashScreenCodes),
  493. "Choose the colour of the JUCE splash screen.");
  494. }
  495. {
  496. StringArray projectTypeNames;
  497. Array<var> projectTypeCodes;
  498. const Array<ProjectType*>& types = ProjectType::getAllTypes();
  499. for (int i = 0; i < types.size(); ++i)
  500. {
  501. projectTypeNames.add (types.getUnchecked(i)->getDescription());
  502. projectTypeCodes.add (types.getUnchecked(i)->getType());
  503. }
  504. props.add (new ChoicePropertyComponent (getProjectTypeValue(), "Project Type", projectTypeNames, projectTypeCodes));
  505. }
  506. props.add (new TextPropertyComponent (getBundleIdentifier(), "Bundle Identifier", 256, false),
  507. "A unique identifier for this product, mainly for use in OSX/iOS builds. It should be something like 'com.yourcompanyname.yourproductname'");
  508. if (getProjectType().isAudioPlugin())
  509. createAudioPluginPropertyEditors (props);
  510. {
  511. const int maxSizes[] = { 20480, 10240, 6144, 2048, 1024, 512, 256, 128, 64 };
  512. StringArray maxSizeNames;
  513. Array<var> maxSizeCodes;
  514. maxSizeNames.add (TRANS("Default"));
  515. maxSizeCodes.add (var());
  516. maxSizeNames.add (String());
  517. maxSizeCodes.add (var());
  518. for (int i = 0; i < numElementsInArray (maxSizes); ++i)
  519. {
  520. const int sizeInBytes = maxSizes[i] * 1024;
  521. maxSizeNames.add (File::descriptionOfSizeInBytes (sizeInBytes));
  522. maxSizeCodes.add (sizeInBytes);
  523. }
  524. props.add (new ChoicePropertyComponent (getMaxBinaryFileSize(), "BinaryData.cpp size limit", maxSizeNames, maxSizeCodes),
  525. "When splitting binary data into multiple cpp files, the Projucer attempts to keep the file sizes below this threshold. "
  526. "(Note that individual resource files which are larger than this size cannot be split across multiple cpp files).");
  527. }
  528. props.add (new BooleanPropertyComponent (shouldIncludeBinaryInAppConfig(), "Include Binary",
  529. "Include BinaryData.h in the AppConfig.h file"));
  530. props.add (new TextPropertyComponent (binaryDataNamespace(), "BinaryData Namespace", 256, false),
  531. "The namespace containing the binary assests. If left empty this defaults to \"BinaryData\".");
  532. props.add (new TextPropertyComponent (getProjectPreprocessorDefs(), "Preprocessor definitions", 32768, true),
  533. "Global preprocessor definitions. Use the form \"NAME1=value NAME2=value\", using whitespace, commas, or "
  534. "new-lines to separate the items - to include a space or comma in a definition, precede it with a backslash.");
  535. props.add (new TextPropertyComponent (getProjectUserNotes(), "Notes", 32768, true),
  536. "Extra comments: This field is not used for code or project generation, it's just a space where you can express your thoughts.");
  537. }
  538. void Project::createAudioPluginPropertyEditors (PropertyListBuilder& props)
  539. {
  540. props.add (new BooleanPropertyComponent (getShouldBuildVSTAsValue(), "Build VST", "Enabled"),
  541. "Whether the project should produce a VST plugin.");
  542. props.add (new BooleanPropertyComponent (getShouldBuildVST3AsValue(), "Build VST3", "Enabled"),
  543. "Whether the project should produce a VST3 plugin.");
  544. props.add (new BooleanPropertyComponent (getShouldBuildAUAsValue(), "Build AudioUnit", "Enabled"),
  545. "Whether the project should produce an AudioUnit plugin.");
  546. props.add (new BooleanPropertyComponent (getShouldBuildAUv3AsValue(), "Build AudioUnit v3", "Enabled"),
  547. "Whether the project should produce an AudioUnit version 3 plugin.");
  548. props.add (new BooleanPropertyComponent (getShouldBuildRTASAsValue(), "Build RTAS", "Enabled"),
  549. "Whether the project should produce an RTAS plugin.");
  550. props.add (new BooleanPropertyComponent (getShouldBuildAAXAsValue(), "Build AAX", "Enabled"),
  551. "Whether the project should produce an AAX plugin.");
  552. props.add (new BooleanPropertyComponent (getShouldBuildStandalonePluginAsValue(), "Build Standalone Plug-In", "Enabled"),
  553. "Whether the project should produce a standalone version of your plugin.");
  554. props.add (new BooleanPropertyComponent (getShouldEnableIAAAsValue(), "Enable Inter-App Audio", "Enabled"),
  555. "Whether a standalone plug-in should be an Inter-App Audio app. You should also enable the audio "
  556. "background capability in the iOS exporter.");
  557. props.add (new TextPropertyComponent (getPluginName(), "Plugin Name", 128, false),
  558. "The name of your plugin (keep it short!)");
  559. props.add (new TextPropertyComponent (getPluginDesc(), "Plugin Description", 256, false),
  560. "A short description of your plugin.");
  561. props.add (new TextPropertyComponent (getPluginManufacturer(), "Plugin Manufacturer", 256, false),
  562. "The name of your company (cannot be blank).");
  563. props.add (new TextPropertyComponent (getPluginManufacturerCode(), "Plugin Manufacturer Code", 4, false),
  564. "A four-character unique ID for your company. Note that for AU compatibility, this must contain at least one upper-case letter!");
  565. props.add (new TextPropertyComponent (getPluginCode(), "Plugin Code", 4, false),
  566. "A four-character unique ID for your plugin. Note that for AU compatibility, this must contain at least one upper-case letter!");
  567. props.add (new TextPropertyComponent (getPluginChannelConfigs(), "Plugin Channel Configurations", 1024, false),
  568. "This list is a comma-separated set list in the form {numIns, numOuts} and each pair indicates a valid plug-in "
  569. "configuration. For example {1, 1}, {2, 2} means that the plugin can be used either with 1 input and 1 output, "
  570. "or with 2 inputs and 2 outputs. If your plug-in requires side-chains, aux output buses etc., then you must leave "
  571. "this field empty and override the setPreferredBusArrangement method in your AudioProcessor.");
  572. props.add (new BooleanPropertyComponent (getPluginIsSynth(), "Plugin is a Synth", "Is a Synth"),
  573. "Enable this if you want your plugin to be treated as a synth or generator. It doesn't make much difference to the plugin itself, but some hosts treat synths differently to other plugins.");
  574. props.add (new BooleanPropertyComponent (getPluginWantsMidiInput(), "Plugin Midi Input", "Plugin wants midi input"),
  575. "Enable this if you want your plugin to accept midi messages.");
  576. props.add (new BooleanPropertyComponent (getPluginProducesMidiOut(), "Plugin Midi Output", "Plugin produces midi output"),
  577. "Enable this if your plugin is going to produce midi messages.");
  578. props.add (new BooleanPropertyComponent (getPluginIsMidiEffectPlugin(), "Midi Effect Plugin", "Plugin is a midi effect plugin"),
  579. "Enable this if your plugin only processes midi and no audio.");
  580. props.add (new BooleanPropertyComponent (getPluginEditorNeedsKeyFocus(), "Key Focus", "Plugin editor requires keyboard focus"),
  581. "Enable this if your plugin needs keyboard input - some hosts can be a bit funny about keyboard focus..");
  582. props.add (new TextPropertyComponent (getPluginAUExportPrefix(), "Plugin AU Export Prefix", 64, false),
  583. "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.");
  584. props.add (new TextPropertyComponent (getPluginAUMainType(), "Plugin AU Main Type", 128, false),
  585. "In an AU, this is the value that is set as JucePlugin_AUMainType. Leave it blank unless you want to use a custom value.");
  586. props.add (new TextPropertyComponent (getPluginVSTCategory(), "VST Category", 64, false),
  587. "In a VST, this is the value that is set as JucePlugin_VSTCategory. Leave it blank unless you want to use a custom value.");
  588. props.add (new TextPropertyComponent (getPluginRTASCategory(), "Plugin RTAS Category", 64, false),
  589. "(Leave this blank if your plugin is a synth). This is one of the RTAS categories from FicPluginEnums.h, such as: ePlugInCategory_None, ePlugInCategory_EQ, ePlugInCategory_Dynamics, "
  590. "ePlugInCategory_PitchShift, ePlugInCategory_Reverb, ePlugInCategory_Delay, "
  591. "ePlugInCategory_Modulation, ePlugInCategory_Harmonic, ePlugInCategory_NoiseReduction, "
  592. "ePlugInCategory_Dither, ePlugInCategory_SoundField");
  593. props.add (new TextPropertyComponent (getPluginAAXCategory(), "Plugin AAX Category", 64, false),
  594. "This is one of the categories from the AAX_EPlugInCategory enum");
  595. props.add (new TextPropertyComponent (getAAXIdentifier(), "Plugin AAX Identifier", 256, false),
  596. "The value to use for the JucePlugin_AAXIdentifier setting");
  597. }
  598. //==============================================================================
  599. static StringArray getVersionSegments (const Project& p)
  600. {
  601. StringArray segments;
  602. segments.addTokens (p.getVersionString(), ",.", "");
  603. segments.trim();
  604. segments.removeEmptyStrings();
  605. return segments;
  606. }
  607. int Project::getVersionAsHexInteger() const
  608. {
  609. const StringArray segments (getVersionSegments (*this));
  610. int value = (segments[0].getIntValue() << 16)
  611. + (segments[1].getIntValue() << 8)
  612. + segments[2].getIntValue();
  613. if (segments.size() >= 4)
  614. value = (value << 8) + segments[3].getIntValue();
  615. return value;
  616. }
  617. String Project::getVersionAsHex() const
  618. {
  619. return "0x" + String::toHexString (getVersionAsHexInteger());
  620. }
  621. StringPairArray Project::getPreprocessorDefs() const
  622. {
  623. return parsePreprocessorDefs (projectRoot [Ids::defines]);
  624. }
  625. File Project::getBinaryDataCppFile (int index) const
  626. {
  627. const File cpp (getGeneratedCodeFolder().getChildFile ("BinaryData.cpp"));
  628. if (index > 0)
  629. return cpp.getSiblingFile (cpp.getFileNameWithoutExtension() + String (index + 1))
  630. .withFileExtension (cpp.getFileExtension());
  631. return cpp;
  632. }
  633. Project::Item Project::getMainGroup()
  634. {
  635. return Item (*this, projectRoot.getChildWithName (Ids::MAINGROUP), false);
  636. }
  637. PropertiesFile& Project::getStoredProperties() const
  638. {
  639. return getAppSettings().getProjectProperties (getProjectUID());
  640. }
  641. static void findImages (const Project::Item& item, OwnedArray<Project::Item>& found)
  642. {
  643. if (item.isImageFile())
  644. {
  645. found.add (new Project::Item (item));
  646. }
  647. else if (item.isGroup())
  648. {
  649. for (int i = 0; i < item.getNumChildren(); ++i)
  650. findImages (item.getChild (i), found);
  651. }
  652. }
  653. void Project::findAllImageItems (OwnedArray<Project::Item>& items)
  654. {
  655. findImages (getMainGroup(), items);
  656. }
  657. //==============================================================================
  658. Project::Item::Item (Project& p, const ValueTree& s, bool isModuleCode)
  659. : project (p), state (s), belongsToModule (isModuleCode)
  660. {
  661. }
  662. Project::Item::Item (const Item& other)
  663. : project (other.project), state (other.state), belongsToModule (other.belongsToModule)
  664. {
  665. }
  666. Project::Item Project::Item::createCopy() { Item i (*this); i.state = i.state.createCopy(); return i; }
  667. String Project::Item::getID() const { return state [Ids::ID]; }
  668. void Project::Item::setID (const String& newID) { state.setProperty (Ids::ID, newID, nullptr); }
  669. Drawable* Project::Item::loadAsImageFile() const
  670. {
  671. return isValid() ? Drawable::createFromImageFile (getFile())
  672. : nullptr;
  673. }
  674. Project::Item Project::Item::createGroup (Project& project, const String& name, const String& uid, bool isModuleCode)
  675. {
  676. Item group (project, ValueTree (Ids::GROUP), isModuleCode);
  677. group.setID (uid);
  678. group.initialiseMissingProperties();
  679. group.getNameValue() = name;
  680. return group;
  681. }
  682. bool Project::Item::isFile() const { return state.hasType (Ids::FILE); }
  683. bool Project::Item::isGroup() const { return state.hasType (Ids::GROUP) || isMainGroup(); }
  684. bool Project::Item::isMainGroup() const { return state.hasType (Ids::MAINGROUP); }
  685. bool Project::Item::isImageFile() const
  686. {
  687. return isFile() && (ImageFileFormat::findImageFormatForFileExtension (getFile()) != nullptr
  688. || getFile().hasFileExtension ("svg"));
  689. }
  690. Project::Item Project::Item::findItemWithID (const String& targetId) const
  691. {
  692. if (state [Ids::ID] == targetId)
  693. return *this;
  694. if (isGroup())
  695. {
  696. for (int i = getNumChildren(); --i >= 0;)
  697. {
  698. Item found (getChild(i).findItemWithID (targetId));
  699. if (found.isValid())
  700. return found;
  701. }
  702. }
  703. return Item (project, ValueTree(), false);
  704. }
  705. bool Project::Item::canContain (const Item& child) const
  706. {
  707. if (isFile())
  708. return false;
  709. if (isGroup())
  710. return child.isFile() || child.isGroup();
  711. jassertfalse;
  712. return false;
  713. }
  714. bool Project::Item::shouldBeAddedToTargetProject() const { return isFile(); }
  715. Value Project::Item::getShouldCompileValue() { return state.getPropertyAsValue (Ids::compile, getUndoManager()); }
  716. bool Project::Item::shouldBeCompiled() const { return state [Ids::compile]; }
  717. Value Project::Item::getShouldAddToBinaryResourcesValue() { return state.getPropertyAsValue (Ids::resource, getUndoManager()); }
  718. bool Project::Item::shouldBeAddedToBinaryResources() const { return state [Ids::resource]; }
  719. Value Project::Item::getShouldAddToXcodeResourcesValue() { return state.getPropertyAsValue (Ids::xcodeResource, getUndoManager()); }
  720. bool Project::Item::shouldBeAddedToXcodeResources() const { return state [Ids::xcodeResource]; }
  721. Value Project::Item::getShouldInhibitWarningsValue() { return state.getPropertyAsValue (Ids::noWarnings, getUndoManager()); }
  722. bool Project::Item::shouldInhibitWarnings() const { return state [Ids::noWarnings]; }
  723. bool Project::Item::isModuleCode() const { return belongsToModule; }
  724. String Project::Item::getFilePath() const
  725. {
  726. if (isFile())
  727. return state [Ids::file].toString();
  728. return {};
  729. }
  730. File Project::Item::getFile() const
  731. {
  732. if (isFile())
  733. return project.resolveFilename (state [Ids::file].toString());
  734. return {};
  735. }
  736. void Project::Item::setFile (const File& file)
  737. {
  738. setFile (RelativePath (project.getRelativePathForFile (file), RelativePath::projectFolder));
  739. jassert (getFile() == file);
  740. }
  741. void Project::Item::setFile (const RelativePath& file)
  742. {
  743. jassert (isFile());
  744. state.setProperty (Ids::file, file.toUnixStyle(), getUndoManager());
  745. state.setProperty (Ids::name, file.getFileName(), getUndoManager());
  746. }
  747. bool Project::Item::renameFile (const File& newFile)
  748. {
  749. const File oldFile (getFile());
  750. if (oldFile.moveFileTo (newFile)
  751. || (newFile.exists() && ! oldFile.exists()))
  752. {
  753. setFile (newFile);
  754. ProjucerApplication::getApp().openDocumentManager.fileHasBeenRenamed (oldFile, newFile);
  755. return true;
  756. }
  757. return false;
  758. }
  759. bool Project::Item::containsChildForFile (const RelativePath& file) const
  760. {
  761. return state.getChildWithProperty (Ids::file, file.toUnixStyle()).isValid();
  762. }
  763. Project::Item Project::Item::findItemForFile (const File& file) const
  764. {
  765. if (getFile() == file)
  766. return *this;
  767. if (isGroup())
  768. {
  769. for (int i = getNumChildren(); --i >= 0;)
  770. {
  771. Item found (getChild(i).findItemForFile (file));
  772. if (found.isValid())
  773. return found;
  774. }
  775. }
  776. return Item (project, ValueTree(), false);
  777. }
  778. File Project::Item::determineGroupFolder() const
  779. {
  780. jassert (isGroup());
  781. File f;
  782. for (int i = 0; i < getNumChildren(); ++i)
  783. {
  784. f = getChild(i).getFile();
  785. if (f.exists())
  786. return f.getParentDirectory();
  787. }
  788. Item parent (getParent());
  789. if (parent != *this)
  790. {
  791. f = parent.determineGroupFolder();
  792. if (f.getChildFile (getName()).isDirectory())
  793. f = f.getChildFile (getName());
  794. }
  795. else
  796. {
  797. f = project.getProjectFolder();
  798. if (f.getChildFile ("Source").isDirectory())
  799. f = f.getChildFile ("Source");
  800. }
  801. return f;
  802. }
  803. void Project::Item::initialiseMissingProperties()
  804. {
  805. if (! state.hasProperty (Ids::ID))
  806. setID (createAlphaNumericUID());
  807. if (isFile())
  808. {
  809. state.setProperty (Ids::name, getFile().getFileName(), nullptr);
  810. }
  811. else if (isGroup())
  812. {
  813. for (int i = getNumChildren(); --i >= 0;)
  814. getChild(i).initialiseMissingProperties();
  815. }
  816. }
  817. Value Project::Item::getNameValue()
  818. {
  819. return state.getPropertyAsValue (Ids::name, getUndoManager());
  820. }
  821. String Project::Item::getName() const
  822. {
  823. return state [Ids::name];
  824. }
  825. void Project::Item::addChild (const Item& newChild, int insertIndex)
  826. {
  827. state.addChild (newChild.state, insertIndex, getUndoManager());
  828. }
  829. void Project::Item::removeItemFromProject()
  830. {
  831. state.getParent().removeChild (state, getUndoManager());
  832. }
  833. Project::Item Project::Item::getParent() const
  834. {
  835. if (isMainGroup() || ! isGroup())
  836. return *this;
  837. return Item (project, state.getParent(), belongsToModule);
  838. }
  839. struct ItemSorter
  840. {
  841. static int compareElements (const ValueTree& first, const ValueTree& second)
  842. {
  843. return first [Ids::name].toString().compareNatural (second [Ids::name].toString());
  844. }
  845. };
  846. struct ItemSorterWithGroupsAtStart
  847. {
  848. static int compareElements (const ValueTree& first, const ValueTree& second)
  849. {
  850. const bool firstIsGroup = first.hasType (Ids::GROUP);
  851. const bool secondIsGroup = second.hasType (Ids::GROUP);
  852. if (firstIsGroup == secondIsGroup)
  853. return first [Ids::name].toString().compareNatural (second [Ids::name].toString());
  854. return firstIsGroup ? -1 : 1;
  855. }
  856. };
  857. static void sortGroup (ValueTree& state, bool keepGroupsAtStart, UndoManager* undoManager)
  858. {
  859. if (keepGroupsAtStart)
  860. {
  861. ItemSorterWithGroupsAtStart sorter;
  862. state.sort (sorter, undoManager, true);
  863. }
  864. else
  865. {
  866. ItemSorter sorter;
  867. state.sort (sorter, undoManager, true);
  868. }
  869. }
  870. static bool isGroupSorted (const ValueTree& state, bool keepGroupsAtStart)
  871. {
  872. if (state.getNumChildren() == 0)
  873. return false;
  874. if (state.getNumChildren() == 1)
  875. return true;
  876. ValueTree stateCopy (state.createCopy());
  877. sortGroup (stateCopy, keepGroupsAtStart, nullptr);
  878. return stateCopy.isEquivalentTo (state);
  879. }
  880. void Project::Item::sortAlphabetically (bool keepGroupsAtStart, bool recursive)
  881. {
  882. sortGroup (state, keepGroupsAtStart, getUndoManager());
  883. if (recursive)
  884. for (int i = getNumChildren(); --i >= 0;)
  885. getChild(i).sortAlphabetically (keepGroupsAtStart, true);
  886. }
  887. Project::Item Project::Item::getOrCreateSubGroup (const String& name)
  888. {
  889. for (int i = state.getNumChildren(); --i >= 0;)
  890. {
  891. const ValueTree child (state.getChild (i));
  892. if (child.getProperty (Ids::name) == name && child.hasType (Ids::GROUP))
  893. return Item (project, child, belongsToModule);
  894. }
  895. return addNewSubGroup (name, -1);
  896. }
  897. Project::Item Project::Item::addNewSubGroup (const String& name, int insertIndex)
  898. {
  899. String newID (createGUID (getID() + name + String (getNumChildren())));
  900. int n = 0;
  901. while (project.getMainGroup().findItemWithID (newID).isValid())
  902. newID = createGUID (newID + String (++n));
  903. Item group (createGroup (project, name, newID, belongsToModule));
  904. jassert (canContain (group));
  905. addChild (group, insertIndex);
  906. return group;
  907. }
  908. bool Project::Item::addFileAtIndex (const File& file, int insertIndex, const bool shouldCompile)
  909. {
  910. if (file == File() || file.isHidden() || file.getFileName().startsWithChar ('.'))
  911. return false;
  912. if (file.isDirectory())
  913. {
  914. Item group (addNewSubGroup (file.getFileName(), insertIndex));
  915. for (DirectoryIterator iter (file, false, "*", File::findFilesAndDirectories); iter.next();)
  916. if (! project.getMainGroup().findItemForFile (iter.getFile()).isValid())
  917. group.addFileRetainingSortOrder (iter.getFile(), shouldCompile);
  918. }
  919. else if (file.existsAsFile())
  920. {
  921. if (! project.getMainGroup().findItemForFile (file).isValid())
  922. addFileUnchecked (file, insertIndex, shouldCompile);
  923. }
  924. else
  925. {
  926. jassertfalse;
  927. }
  928. return true;
  929. }
  930. bool Project::Item::addFileRetainingSortOrder (const File& file, bool shouldCompile)
  931. {
  932. const bool wasSortedGroupsNotFirst = isGroupSorted (state, false);
  933. const bool wasSortedGroupsFirst = isGroupSorted (state, true);
  934. if (! addFileAtIndex (file, 0, shouldCompile))
  935. return false;
  936. if (wasSortedGroupsNotFirst || wasSortedGroupsFirst)
  937. sortAlphabetically (wasSortedGroupsFirst, false);
  938. return true;
  939. }
  940. void Project::Item::addFileUnchecked (const File& file, int insertIndex, const bool shouldCompile)
  941. {
  942. Item item (project, ValueTree (Ids::FILE), belongsToModule);
  943. item.initialiseMissingProperties();
  944. item.getNameValue() = file.getFileName();
  945. item.getShouldCompileValue() = shouldCompile && file.hasFileExtension (fileTypesToCompileByDefault);
  946. item.getShouldAddToBinaryResourcesValue() = project.shouldBeAddedToBinaryResourcesByDefault (file);
  947. if (canContain (item))
  948. {
  949. item.setFile (file);
  950. addChild (item, insertIndex);
  951. }
  952. }
  953. bool Project::Item::addRelativeFile (const RelativePath& file, int insertIndex, bool shouldCompile)
  954. {
  955. Item item (project, ValueTree (Ids::FILE), belongsToModule);
  956. item.initialiseMissingProperties();
  957. item.getNameValue() = file.getFileName();
  958. item.getShouldCompileValue() = shouldCompile;
  959. item.getShouldAddToBinaryResourcesValue() = project.shouldBeAddedToBinaryResourcesByDefault (file);
  960. if (canContain (item))
  961. {
  962. item.setFile (file);
  963. addChild (item, insertIndex);
  964. return true;
  965. }
  966. return false;
  967. }
  968. Icon Project::Item::getIcon (bool isOpen) const
  969. {
  970. const Icons& icons = getIcons();
  971. if (isFile())
  972. {
  973. if (isImageFile())
  974. return Icon (icons.imageDoc, Colours::transparentBlack);
  975. return Icon (icons.file, Colours::transparentBlack);
  976. }
  977. if (isMainGroup())
  978. return Icon (icons.juceLogo, Colours::orange);
  979. return Icon (isOpen ? icons.openFolder : icons.closedFolder, Colours::transparentBlack);
  980. }
  981. bool Project::Item::isIconCrossedOut() const
  982. {
  983. return isFile()
  984. && ! (shouldBeCompiled()
  985. || shouldBeAddedToBinaryResources()
  986. || getFile().hasFileExtension (headerFileExtensions));
  987. }
  988. //==============================================================================
  989. ValueTree Project::getConfigNode()
  990. {
  991. return projectRoot.getOrCreateChildWithName (Ids::JUCEOPTIONS, nullptr);
  992. }
  993. const char* const Project::configFlagDefault = "default";
  994. const char* const Project::configFlagEnabled = "enabled";
  995. const char* const Project::configFlagDisabled = "disabled";
  996. Value Project::getConfigFlag (const String& name)
  997. {
  998. ValueTree configNode (getConfigNode());
  999. Value v (configNode.getPropertyAsValue (name, getUndoManagerFor (configNode)));
  1000. if (v.getValue().toString().isEmpty())
  1001. v = configFlagDefault;
  1002. return v;
  1003. }
  1004. bool Project::isConfigFlagEnabled (const String& name) const
  1005. {
  1006. return projectRoot.getChildWithName (Ids::JUCEOPTIONS).getProperty (name) == configFlagEnabled;
  1007. }
  1008. void Project::sanitiseConfigFlags()
  1009. {
  1010. ValueTree configNode (getConfigNode());
  1011. for (int i = configNode.getNumProperties(); --i >= 0;)
  1012. {
  1013. const var value (configNode [configNode.getPropertyName(i)]);
  1014. if (value != configFlagEnabled && value != configFlagDisabled)
  1015. configNode.removeProperty (configNode.getPropertyName(i), getUndoManagerFor (configNode));
  1016. }
  1017. }
  1018. //==============================================================================
  1019. String Project::getPluginRTASCategoryCode()
  1020. {
  1021. if (static_cast<bool> (getPluginIsSynth().getValue()))
  1022. return "ePlugInCategory_SWGenerators";
  1023. String s (getPluginRTASCategory().toString());
  1024. if (s.isEmpty())
  1025. s = "ePlugInCategory_None";
  1026. return s;
  1027. }
  1028. String Project::getAUMainTypeString()
  1029. {
  1030. String s (getPluginAUMainType().toString());
  1031. if (s.isEmpty())
  1032. {
  1033. // Unfortunately, Rez uses a header where kAudioUnitType_MIDIProcessor is undefined
  1034. // Use aumi instead.
  1035. if (getPluginIsMidiEffectPlugin().getValue()) s = "'aumi'";
  1036. else if (getPluginIsSynth().getValue()) s = "kAudioUnitType_MusicDevice";
  1037. else if (getPluginWantsMidiInput().getValue()) s = "kAudioUnitType_MusicEffect";
  1038. else s = "kAudioUnitType_Effect";
  1039. }
  1040. return s;
  1041. }
  1042. String Project::getAUMainTypeCode()
  1043. {
  1044. String s (getPluginAUMainType().toString());
  1045. if (s.isEmpty())
  1046. {
  1047. if (getPluginIsMidiEffectPlugin().getValue()) s = "aumi";
  1048. else if (getPluginIsSynth().getValue()) s = "aumu";
  1049. else if (getPluginWantsMidiInput().getValue()) s = "aumf";
  1050. else s = "aufx";
  1051. }
  1052. return s;
  1053. }
  1054. String Project::getIAATypeCode()
  1055. {
  1056. String s;
  1057. if (getPluginWantsMidiInput().getValue())
  1058. {
  1059. if (getPluginIsSynth().getValue())
  1060. s = "auri";
  1061. else
  1062. s = "aurm";
  1063. }
  1064. else
  1065. {
  1066. if (getPluginIsSynth().getValue())
  1067. s = "aurg";
  1068. else
  1069. s = "aurx";
  1070. }
  1071. return s;
  1072. }
  1073. String Project::getIAAPluginName()
  1074. {
  1075. String s = getPluginManufacturer().toString();
  1076. s << ": ";
  1077. s << getPluginName().toString();
  1078. return s;
  1079. }
  1080. String Project::getPluginVSTCategoryString()
  1081. {
  1082. String s (getPluginVSTCategory().toString().trim());
  1083. if (s.isEmpty())
  1084. s = static_cast<bool> (getPluginIsSynth().getValue()) ? "kPlugCategSynth"
  1085. : "kPlugCategEffect";
  1086. return s;
  1087. }
  1088. bool Project::isAUPluginHost()
  1089. {
  1090. return getModules().isModuleEnabled ("juce_audio_processors") && isConfigFlagEnabled ("JUCE_PLUGINHOST_AU");
  1091. }
  1092. bool Project::isVSTPluginHost()
  1093. {
  1094. return getModules().isModuleEnabled ("juce_audio_processors") && isConfigFlagEnabled ("JUCE_PLUGINHOST_VST");
  1095. }
  1096. bool Project::isVST3PluginHost()
  1097. {
  1098. return getModules().isModuleEnabled ("juce_audio_processors") && isConfigFlagEnabled ("JUCE_PLUGINHOST_VST3");
  1099. }
  1100. //==============================================================================
  1101. EnabledModuleList& Project::getModules()
  1102. {
  1103. if (enabledModulesList == nullptr)
  1104. enabledModulesList = new EnabledModuleList (*this, projectRoot.getOrCreateChildWithName (Ids::MODULES, nullptr));
  1105. return *enabledModulesList;
  1106. }
  1107. //==============================================================================
  1108. ValueTree Project::getExporters()
  1109. {
  1110. return projectRoot.getOrCreateChildWithName (Ids::EXPORTFORMATS, nullptr);
  1111. }
  1112. int Project::getNumExporters()
  1113. {
  1114. return getExporters().getNumChildren();
  1115. }
  1116. ProjectExporter* Project::createExporter (int index)
  1117. {
  1118. jassert (index >= 0 && index < getNumExporters());
  1119. return ProjectExporter::createExporter (*this, getExporters().getChild (index));
  1120. }
  1121. void Project::addNewExporter (const String& exporterName)
  1122. {
  1123. ScopedPointer<ProjectExporter> exp (ProjectExporter::createNewExporter (*this, exporterName));
  1124. ValueTree exporters (getExporters());
  1125. exporters.addChild (exp->settings, -1, getUndoManagerFor (exporters));
  1126. }
  1127. void Project::createExporterForCurrentPlatform()
  1128. {
  1129. addNewExporter (ProjectExporter::getCurrentPlatformExporterName());
  1130. }
  1131. //==============================================================================
  1132. String Project::getFileTemplate (const String& templateName)
  1133. {
  1134. int dataSize;
  1135. if (const char* data = BinaryData::getNamedResource (templateName.toUTF8(), dataSize))
  1136. return String::fromUTF8 (data, dataSize);
  1137. jassertfalse;
  1138. return {};
  1139. }
  1140. //==============================================================================
  1141. Project::ExporterIterator::ExporterIterator (Project& p) : index (-1), project (p) {}
  1142. Project::ExporterIterator::~ExporterIterator() {}
  1143. bool Project::ExporterIterator::next()
  1144. {
  1145. if (++index >= project.getNumExporters())
  1146. return false;
  1147. exporter = project.createExporter (index);
  1148. if (exporter == nullptr)
  1149. {
  1150. jassertfalse; // corrupted project file?
  1151. return next();
  1152. }
  1153. return true;
  1154. }