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.

284 lines
12KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2020 - Raw Material Software Limited
  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 6 End-User License
  8. Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
  9. End User License Agreement: www.juce.com/juce-6-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. #include "../jucer_Headers.h"
  19. #include "../jucer_Application.h"
  20. #include "../../ProjectSaving/jucer_ProjectExporter.h"
  21. #include "jucer_NewProjectWizard.h"
  22. //==============================================================================
  23. static String getFileTemplate (const String& templateName)
  24. {
  25. int dataSize = 0;
  26. if (auto* data = BinaryData::getNamedResource (templateName.toUTF8(), dataSize))
  27. return String::fromUTF8 (data, dataSize);
  28. jassertfalse;
  29. return {};
  30. }
  31. static String getJuceHeaderInclude()
  32. {
  33. return CodeHelpers::createIncludePathIncludeStatement (Project::getJuceSourceHFilename());
  34. }
  35. static String getContentComponentName()
  36. {
  37. return "MainComponent";
  38. }
  39. using Opts = NewProjectTemplates::FileCreationOptions;
  40. static bool shouldCreateHeaderFile (Opts opts) noexcept { return opts == Opts::header || opts == Opts::headerAndCpp; }
  41. static bool shouldCreateCppFile (Opts opts) noexcept { return opts == Opts::headerAndCpp; }
  42. static void doBasicProjectSetup (Project& project, const NewProjectTemplates::ProjectTemplate& projectTemplate, const String& name)
  43. {
  44. project.setTitle (name);
  45. project.setProjectType (projectTemplate.projectTypeString);
  46. project.getMainGroup().addNewSubGroup ("Source", 0);
  47. project.getConfigFlag ("JUCE_STRICT_REFCOUNTEDPOINTER") = true;
  48. project.getConfigFlag ("JUCE_VST3_HOST_CROSS_PLATFORM_UID") = true;
  49. project.getProjectValue (Ids::useAppConfig) = false;
  50. project.getProjectValue (Ids::addUsingNamespaceToJuceHeader) = false;
  51. if (! ProjucerApplication::getApp().getLicenseController().getCurrentState().canUnlockFullFeatures())
  52. project.getProjectValue (Ids::displaySplashScreen) = true;
  53. if (NewProjectTemplates::isPlugin (projectTemplate))
  54. project.getConfigFlag ("JUCE_VST3_CAN_REPLACE_VST2") = 0;
  55. }
  56. static std::map<String, String> getSharedFileTokenReplacements()
  57. {
  58. return { { "%%app_headers%%", getJuceHeaderInclude() } };
  59. }
  60. static std::map<String, String> getApplicationFileTokenReplacements (const String& name,
  61. NewProjectTemplates::FileCreationOptions fileOptions,
  62. const File& sourceFolder)
  63. {
  64. auto tokenReplacements = getSharedFileTokenReplacements();
  65. tokenReplacements.insert ({ "%%app_class_name%%",
  66. build_tools::makeValidIdentifier (name + "Application", false, true, false) });
  67. tokenReplacements.insert ({ "%%content_component_class%%",
  68. getContentComponentName() });
  69. tokenReplacements.insert ({ "%%include_juce%%",
  70. getJuceHeaderInclude() });
  71. if (shouldCreateHeaderFile (fileOptions))
  72. tokenReplacements["%%app_headers%%"] << newLine
  73. << CodeHelpers::createIncludeStatement (sourceFolder.getChildFile ("MainComponent.h"),
  74. sourceFolder.getChildFile ("Main.cpp"));
  75. if (shouldCreateCppFile (fileOptions))
  76. tokenReplacements.insert ({ "%%include_corresponding_header%%",
  77. CodeHelpers::createIncludeStatement (sourceFolder.getChildFile ("MainComponent.h"),
  78. sourceFolder.getChildFile ("MainComponent.cpp")) });
  79. return tokenReplacements;
  80. }
  81. static std::map<String, String> getPluginFileTokenReplacements (const String& name,
  82. const File& sourceFolder)
  83. {
  84. auto tokenReplacements = getSharedFileTokenReplacements();
  85. auto processorCppFile = sourceFolder.getChildFile ("PluginProcessor.cpp");
  86. auto processorHFile = processorCppFile.withFileExtension (".h");
  87. auto editorCppFile = sourceFolder.getChildFile ("PluginEditor.cpp");
  88. auto editorHFile = editorCppFile.withFileExtension (".h");
  89. auto processorHInclude = CodeHelpers::createIncludeStatement (processorHFile, processorCppFile);
  90. auto editorHInclude = CodeHelpers::createIncludeStatement (editorHFile, processorCppFile);
  91. auto processorClassName = build_tools::makeValidIdentifier (name, false, true, false) + "AudioProcessor";
  92. processorClassName = processorClassName.substring (0, 1).toUpperCase() + processorClassName.substring (1);
  93. auto editorClassName = processorClassName + "Editor";
  94. tokenReplacements.insert ({"%%filter_headers%%", processorHInclude + newLine + editorHInclude });
  95. tokenReplacements.insert ({"%%filter_class_name%%", processorClassName });
  96. tokenReplacements.insert ({"%%editor_class_name%%", editorClassName });
  97. tokenReplacements.insert ({"%%editor_cpp_headers%%", processorHInclude + newLine + editorHInclude });
  98. tokenReplacements.insert ({"%%editor_headers%%", getJuceHeaderInclude() + newLine + processorHInclude });
  99. return tokenReplacements;
  100. }
  101. static bool addFiles (Project& project, const NewProjectTemplates::ProjectTemplate& projectTemplate,
  102. const String& name, var fileOptionsVar, StringArray& failedFiles)
  103. {
  104. auto sourceFolder = project.getFile().getSiblingFile ("Source");
  105. if (! sourceFolder.createDirectory())
  106. {
  107. failedFiles.add (sourceFolder.getFullPathName());
  108. return false;
  109. }
  110. auto fileOptions = NewProjectTemplates::getFileOptionForVar (fileOptionsVar);
  111. if (fileOptions == Opts::noFiles)
  112. return true;
  113. auto tokenReplacements = [&]() -> std::map<String, String>
  114. {
  115. if (NewProjectTemplates::isApplication (projectTemplate))
  116. return getApplicationFileTokenReplacements (name, fileOptions, sourceFolder);
  117. if (NewProjectTemplates::isPlugin (projectTemplate))
  118. return getPluginFileTokenReplacements (name, sourceFolder);
  119. jassertfalse;
  120. return {};
  121. }();
  122. auto sourceGroup = project.getMainGroup().getOrCreateSubGroup ("Source");
  123. for (auto& files : projectTemplate.getFilesForOption (fileOptions))
  124. {
  125. auto file = sourceFolder.getChildFile (files.first);
  126. auto fileContent = getFileTemplate (files.second);
  127. for (auto& tokenReplacement : tokenReplacements)
  128. fileContent = fileContent.replace (tokenReplacement.first, tokenReplacement.second, false);
  129. if (! build_tools::overwriteFileWithNewDataIfDifferent (file, fileContent))
  130. {
  131. failedFiles.add (file.getFullPathName());
  132. return false;
  133. }
  134. sourceGroup.addFileAtIndex (file, -1, (file.hasFileExtension (sourceFileExtensions)));
  135. }
  136. return true;
  137. }
  138. static void addModules (Project& project, Array<var> modules, const String& modulePath, bool useGlobalPath)
  139. {
  140. AvailableModulesList list;
  141. list.scanPaths ({ modulePath });
  142. auto& projectModules = project.getEnabledModules();
  143. for (auto& mod : list.getAllModules())
  144. if (modules.contains (mod.first))
  145. projectModules.addModule (mod.second, false, useGlobalPath);
  146. for (auto& mod : projectModules.getModulesWithMissingDependencies())
  147. projectModules.tryToFixMissingDependencies (mod);
  148. }
  149. static void addExporters (Project& project, Array<var> exporters)
  150. {
  151. for (auto exporter : exporters)
  152. project.addNewExporter (exporter.toString());
  153. for (Project::ExporterIterator exporter (project); exporter.next();)
  154. for (ProjectExporter::ConfigIterator config (*exporter); config.next();)
  155. config->getValue (Ids::targetName) = project.getProjectFilenameRootString();
  156. }
  157. //==============================================================================
  158. File NewProjectWizard::getLastWizardFolder()
  159. {
  160. if (getAppSettings().lastWizardFolder.isDirectory())
  161. return getAppSettings().lastWizardFolder;
  162. #if JUCE_WINDOWS
  163. static File lastFolderFallback (File::getSpecialLocation (File::userDocumentsDirectory));
  164. #else
  165. static File lastFolderFallback (File::getSpecialLocation (File::userHomeDirectory));
  166. #endif
  167. return lastFolderFallback;
  168. }
  169. std::unique_ptr<Project> NewProjectWizard::createNewProject (const NewProjectTemplates::ProjectTemplate& projectTemplate,
  170. const File& targetFolder, const String& name, var modules, var exporters, var fileOptions,
  171. const String& modulePath, bool useGlobalModulePath)
  172. {
  173. StringArray failedFiles;
  174. if (! targetFolder.exists())
  175. {
  176. if (! targetFolder.createDirectory())
  177. failedFiles.add (targetFolder.getFullPathName());
  178. }
  179. else if (FileHelpers::containsAnyNonHiddenFiles (targetFolder))
  180. {
  181. if (! AlertWindow::showOkCancelBox (AlertWindow::InfoIcon,
  182. TRANS("New JUCE Project"),
  183. TRANS("You chose the folder:\n\nXFLDRX\n\n").replace ("XFLDRX", targetFolder.getFullPathName())
  184. + TRANS("This folder isn't empty - are you sure you want to create the project there?")
  185. + "\n\n"
  186. + TRANS("Any existing files with the same names may be overwritten by the new files.")))
  187. {
  188. return nullptr;
  189. }
  190. }
  191. auto project = std::make_unique<Project> (targetFolder.getChildFile (File::createLegalFileName (name))
  192. .withFileExtension (Project::projectFileExtension));
  193. if (failedFiles.isEmpty())
  194. {
  195. doBasicProjectSetup (*project, projectTemplate, name);
  196. if (addFiles (*project, projectTemplate, name, fileOptions, failedFiles))
  197. {
  198. addExporters (*project, *exporters.getArray());
  199. addModules (*project, *modules.getArray(), modulePath, useGlobalModulePath);
  200. if (project->save (false, true) == FileBasedDocument::savedOk)
  201. {
  202. project->setChangedFlag (false);
  203. project->loadFrom (project->getFile(), true);
  204. }
  205. else
  206. {
  207. failedFiles.add (project->getFile().getFullPathName());
  208. }
  209. }
  210. }
  211. if (! failedFiles.isEmpty())
  212. {
  213. AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
  214. TRANS("Errors in Creating Project!"),
  215. TRANS("The following files couldn't be written:")
  216. + "\n\n"
  217. + failedFiles.joinIntoString ("\n", 0, 10));
  218. return nullptr;
  219. }
  220. return project;
  221. }