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.

328 lines
14KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - 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 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-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.getProjectValue (Ids::useAppConfig) = false;
  49. project.getProjectValue (Ids::addUsingNamespaceToJuceHeader) = false;
  50. if (! ProjucerApplication::getApp().getLicenseController().getCurrentState().canUnlockFullFeatures())
  51. project.getProjectValue (Ids::displaySplashScreen) = true;
  52. if (NewProjectTemplates::isPlugin (projectTemplate))
  53. project.getConfigFlag ("JUCE_VST3_CAN_REPLACE_VST2") = 0;
  54. }
  55. static std::map<String, String> getSharedFileTokenReplacements()
  56. {
  57. return { { "%%app_headers%%", getJuceHeaderInclude() } };
  58. }
  59. static std::map<String, String> getApplicationFileTokenReplacements (const String& name,
  60. NewProjectTemplates::FileCreationOptions fileOptions,
  61. const File& sourceFolder)
  62. {
  63. auto tokenReplacements = getSharedFileTokenReplacements();
  64. tokenReplacements.insert ({ "%%app_class_name%%",
  65. build_tools::makeValidIdentifier (name + "Application", false, true, false) });
  66. tokenReplacements.insert ({ "%%content_component_class%%",
  67. getContentComponentName() });
  68. tokenReplacements.insert ({ "%%include_juce%%",
  69. getJuceHeaderInclude() });
  70. if (shouldCreateHeaderFile (fileOptions))
  71. tokenReplacements["%%app_headers%%"] << newLine
  72. << CodeHelpers::createIncludeStatement (sourceFolder.getChildFile ("MainComponent.h"),
  73. sourceFolder.getChildFile ("Main.cpp"));
  74. if (shouldCreateCppFile (fileOptions))
  75. tokenReplacements.insert ({ "%%include_corresponding_header%%",
  76. CodeHelpers::createIncludeStatement (sourceFolder.getChildFile ("MainComponent.h"),
  77. sourceFolder.getChildFile ("MainComponent.cpp")) });
  78. return tokenReplacements;
  79. }
  80. static std::map<String, String> getPluginFileTokenReplacements (const String& name,
  81. const File& sourceFolder)
  82. {
  83. auto tokenReplacements = getSharedFileTokenReplacements();
  84. auto processorCppFile = sourceFolder.getChildFile ("PluginProcessor.cpp");
  85. auto processorHFile = processorCppFile.withFileExtension (".h");
  86. auto editorCppFile = sourceFolder.getChildFile ("PluginEditor.cpp");
  87. auto editorHFile = editorCppFile.withFileExtension (".h");
  88. auto processorHInclude = CodeHelpers::createIncludeStatement (processorHFile, processorCppFile);
  89. auto editorHInclude = CodeHelpers::createIncludeStatement (editorHFile, processorCppFile);
  90. auto processorClassName = build_tools::makeValidIdentifier (name, false, true, false) + "AudioProcessor";
  91. processorClassName = processorClassName.substring (0, 1).toUpperCase() + processorClassName.substring (1);
  92. auto editorClassName = processorClassName + "Editor";
  93. const auto araDocumentControllerCppFile = sourceFolder.getChildFile ("PluginARADocumentController.cpp");
  94. const auto araDocumentControllerHFile = araDocumentControllerCppFile.withFileExtension (".h");
  95. const auto araPlaybackRendererCppFile = sourceFolder.getChildFile ("PluginARAPlaybackRenderer.cpp");
  96. const auto araPlaybackRendererHFile = araPlaybackRendererCppFile.withFileExtension (".h");
  97. const auto araDocumentControllerHInclude = CodeHelpers::createIncludeStatement (araDocumentControllerHFile, araDocumentControllerCppFile);
  98. const auto araPlaybackRendererHInclude = CodeHelpers::createIncludeStatement (araPlaybackRendererHFile, araPlaybackRendererCppFile);
  99. auto araDocumentControllerClassName = build_tools::makeValidIdentifier (name, true, true, false) + "DocumentController";
  100. araDocumentControllerClassName = araDocumentControllerClassName.substring (0, 1).toUpperCase() + araDocumentControllerClassName.substring (1);
  101. auto araPlaybackRendererClassName = build_tools::makeValidIdentifier (name, true, true, false) + "PlaybackRenderer";
  102. araPlaybackRendererClassName = araPlaybackRendererClassName.substring (0, 1).toUpperCase() + araPlaybackRendererClassName.substring (1);
  103. tokenReplacements.insert ({"%%filter_headers%%", processorHInclude + newLine + editorHInclude });
  104. tokenReplacements.insert ({"%%filter_class_name%%", processorClassName });
  105. tokenReplacements.insert ({"%%editor_class_name%%", editorClassName });
  106. tokenReplacements.insert ({"%%editor_cpp_headers%%", processorHInclude + newLine + editorHInclude });
  107. tokenReplacements.insert ({"%%editor_headers%%", getJuceHeaderInclude() + newLine + processorHInclude });
  108. tokenReplacements.insert ({"%%aradocumentcontroller_headers%%", araDocumentControllerHInclude });
  109. tokenReplacements.insert ({"%%aradocumentcontroller_class_name%%", araDocumentControllerClassName });
  110. tokenReplacements.insert ({"%%araplaybackrenderer_headers%%", araPlaybackRendererHInclude });
  111. tokenReplacements.insert ({"%%araplaybackrenderer_class_name%%", araPlaybackRendererClassName });
  112. return tokenReplacements;
  113. }
  114. static bool addFiles (Project& project, const NewProjectTemplates::ProjectTemplate& projectTemplate,
  115. const String& name, var fileOptionsVar, StringArray& failedFiles)
  116. {
  117. auto sourceFolder = project.getFile().getSiblingFile ("Source");
  118. if (! sourceFolder.createDirectory())
  119. {
  120. failedFiles.add (sourceFolder.getFullPathName());
  121. return false;
  122. }
  123. auto fileOptions = NewProjectTemplates::getFileOptionForVar (fileOptionsVar);
  124. if (fileOptions == Opts::noFiles)
  125. return true;
  126. auto tokenReplacements = [&]() -> std::map<String, String>
  127. {
  128. if (NewProjectTemplates::isApplication (projectTemplate))
  129. return getApplicationFileTokenReplacements (name, fileOptions, sourceFolder);
  130. if (NewProjectTemplates::isPlugin (projectTemplate))
  131. return getPluginFileTokenReplacements (name, sourceFolder);
  132. jassertfalse;
  133. return {};
  134. }();
  135. auto sourceGroup = project.getMainGroup().getOrCreateSubGroup ("Source");
  136. for (auto& files : projectTemplate.getFilesForOption (fileOptions))
  137. {
  138. auto file = sourceFolder.getChildFile (files.first);
  139. auto fileContent = getFileTemplate (files.second);
  140. for (auto& tokenReplacement : tokenReplacements)
  141. fileContent = fileContent.replace (tokenReplacement.first, tokenReplacement.second, false);
  142. if (! build_tools::overwriteFileWithNewDataIfDifferent (file, fileContent))
  143. {
  144. failedFiles.add (file.getFullPathName());
  145. return false;
  146. }
  147. sourceGroup.addFileAtIndex (file, -1, (file.hasFileExtension (sourceFileExtensions)));
  148. }
  149. return true;
  150. }
  151. static void addModules (Project& project, Array<var> modules, const String& modulePath, bool useGlobalPath)
  152. {
  153. AvailableModulesList list;
  154. list.scanPaths ({ modulePath });
  155. auto& projectModules = project.getEnabledModules();
  156. for (auto& mod : list.getAllModules())
  157. if (modules.contains (mod.first))
  158. projectModules.addModule (mod.second, false, useGlobalPath);
  159. for (auto& mod : projectModules.getModulesWithMissingDependencies())
  160. projectModules.tryToFixMissingDependencies (mod);
  161. }
  162. static void addExporters (Project& project, Array<var> exporters)
  163. {
  164. for (auto exporter : exporters)
  165. project.addNewExporter (exporter.toString());
  166. for (Project::ExporterIterator exporter (project); exporter.next();)
  167. for (ProjectExporter::ConfigIterator config (*exporter); config.next();)
  168. config->getValue (Ids::targetName) = project.getProjectFilenameRootString();
  169. }
  170. //==============================================================================
  171. File NewProjectWizard::getLastWizardFolder()
  172. {
  173. if (getAppSettings().lastWizardFolder.isDirectory())
  174. return getAppSettings().lastWizardFolder;
  175. #if JUCE_WINDOWS
  176. static File lastFolderFallback (File::getSpecialLocation (File::userDocumentsDirectory));
  177. #else
  178. static File lastFolderFallback (File::getSpecialLocation (File::userHomeDirectory));
  179. #endif
  180. return lastFolderFallback;
  181. }
  182. static void displayFailedFilesMessage (const StringArray& failedFiles)
  183. {
  184. AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
  185. TRANS("Errors in Creating Project!"),
  186. TRANS("The following files couldn't be written:")
  187. + "\n\n"
  188. + failedFiles.joinIntoString ("\n", 0, 10));
  189. }
  190. template <typename Callback>
  191. static void prepareDirectory (const File& targetFolder, Callback&& callback)
  192. {
  193. StringArray failedFiles;
  194. if (! targetFolder.exists())
  195. {
  196. if (! targetFolder.createDirectory())
  197. {
  198. displayFailedFilesMessage ({ targetFolder.getFullPathName() });
  199. return;
  200. }
  201. }
  202. else if (FileHelpers::containsAnyNonHiddenFiles (targetFolder))
  203. {
  204. AlertWindow::showOkCancelBox (MessageBoxIconType::InfoIcon,
  205. TRANS("New JUCE Project"),
  206. TRANS("You chose the folder:\n\nXFLDRX\n\n").replace ("XFLDRX", targetFolder.getFullPathName())
  207. + TRANS("This folder isn't empty - are you sure you want to create the project there?")
  208. + "\n\n"
  209. + TRANS("Any existing files with the same names may be overwritten by the new files."),
  210. {},
  211. {},
  212. nullptr,
  213. ModalCallbackFunction::create ([callback] (int result)
  214. {
  215. if (result != 0)
  216. callback();
  217. }));
  218. return;
  219. }
  220. callback();
  221. }
  222. void NewProjectWizard::createNewProject (const NewProjectTemplates::ProjectTemplate& projectTemplate,
  223. const File& targetFolder, const String& name, var modules, var exporters, var fileOptions,
  224. const String& modulePath, bool useGlobalModulePath,
  225. std::function<void (std::unique_ptr<Project>)> callback)
  226. {
  227. prepareDirectory (targetFolder, [=]
  228. {
  229. auto project = std::make_unique<Project> (targetFolder.getChildFile (File::createLegalFileName (name))
  230. .withFileExtension (Project::projectFileExtension));
  231. doBasicProjectSetup (*project, projectTemplate, name);
  232. StringArray failedFiles;
  233. if (addFiles (*project, projectTemplate, name, fileOptions, failedFiles))
  234. {
  235. addExporters (*project, *exporters.getArray());
  236. addModules (*project, *modules.getArray(), modulePath, useGlobalModulePath);
  237. auto sharedProject = std::make_shared<std::unique_ptr<Project>> (std::move (project));
  238. (*sharedProject)->saveAsync (false, true, [sharedProject, failedFiles, callback] (FileBasedDocument::SaveResult r)
  239. {
  240. auto uniqueProject = std::move (*sharedProject.get());
  241. if (r == FileBasedDocument::savedOk)
  242. {
  243. uniqueProject->setChangedFlag (false);
  244. uniqueProject->loadFrom (uniqueProject->getFile(), true);
  245. callback (std::move (uniqueProject));
  246. return;
  247. }
  248. auto failedFilesCopy = failedFiles;
  249. failedFilesCopy.add (uniqueProject->getFile().getFullPathName());
  250. displayFailedFilesMessage (failedFilesCopy);
  251. });
  252. return;
  253. }
  254. displayFailedFilesMessage (failedFiles);
  255. });
  256. }