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.

311 lines
13KB

  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.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. tokenReplacements.insert ({"%%filter_headers%%", processorHInclude + newLine + editorHInclude });
  94. tokenReplacements.insert ({"%%filter_class_name%%", processorClassName });
  95. tokenReplacements.insert ({"%%editor_class_name%%", editorClassName });
  96. tokenReplacements.insert ({"%%editor_cpp_headers%%", processorHInclude + newLine + editorHInclude });
  97. tokenReplacements.insert ({"%%editor_headers%%", getJuceHeaderInclude() + newLine + processorHInclude });
  98. return tokenReplacements;
  99. }
  100. static bool addFiles (Project& project, const NewProjectTemplates::ProjectTemplate& projectTemplate,
  101. const String& name, var fileOptionsVar, StringArray& failedFiles)
  102. {
  103. auto sourceFolder = project.getFile().getSiblingFile ("Source");
  104. if (! sourceFolder.createDirectory())
  105. {
  106. failedFiles.add (sourceFolder.getFullPathName());
  107. return false;
  108. }
  109. auto fileOptions = NewProjectTemplates::getFileOptionForVar (fileOptionsVar);
  110. if (fileOptions == Opts::noFiles)
  111. return true;
  112. auto tokenReplacements = [&]() -> std::map<String, String>
  113. {
  114. if (NewProjectTemplates::isApplication (projectTemplate))
  115. return getApplicationFileTokenReplacements (name, fileOptions, sourceFolder);
  116. if (NewProjectTemplates::isPlugin (projectTemplate))
  117. return getPluginFileTokenReplacements (name, sourceFolder);
  118. jassertfalse;
  119. return {};
  120. }();
  121. auto sourceGroup = project.getMainGroup().getOrCreateSubGroup ("Source");
  122. for (auto& files : projectTemplate.getFilesForOption (fileOptions))
  123. {
  124. auto file = sourceFolder.getChildFile (files.first);
  125. auto fileContent = getFileTemplate (files.second);
  126. for (auto& tokenReplacement : tokenReplacements)
  127. fileContent = fileContent.replace (tokenReplacement.first, tokenReplacement.second, false);
  128. if (! build_tools::overwriteFileWithNewDataIfDifferent (file, fileContent))
  129. {
  130. failedFiles.add (file.getFullPathName());
  131. return false;
  132. }
  133. sourceGroup.addFileAtIndex (file, -1, (file.hasFileExtension (sourceFileExtensions)));
  134. }
  135. return true;
  136. }
  137. static void addModules (Project& project, Array<var> modules, const String& modulePath, bool useGlobalPath)
  138. {
  139. AvailableModulesList list;
  140. list.scanPaths ({ modulePath });
  141. auto& projectModules = project.getEnabledModules();
  142. for (auto& mod : list.getAllModules())
  143. if (modules.contains (mod.first))
  144. projectModules.addModule (mod.second, false, useGlobalPath);
  145. for (auto& mod : projectModules.getModulesWithMissingDependencies())
  146. projectModules.tryToFixMissingDependencies (mod);
  147. }
  148. static void addExporters (Project& project, Array<var> exporters)
  149. {
  150. for (auto exporter : exporters)
  151. project.addNewExporter (exporter.toString());
  152. for (Project::ExporterIterator exporter (project); exporter.next();)
  153. for (ProjectExporter::ConfigIterator config (*exporter); config.next();)
  154. config->getValue (Ids::targetName) = project.getProjectFilenameRootString();
  155. }
  156. //==============================================================================
  157. File NewProjectWizard::getLastWizardFolder()
  158. {
  159. if (getAppSettings().lastWizardFolder.isDirectory())
  160. return getAppSettings().lastWizardFolder;
  161. #if JUCE_WINDOWS
  162. static File lastFolderFallback (File::getSpecialLocation (File::userDocumentsDirectory));
  163. #else
  164. static File lastFolderFallback (File::getSpecialLocation (File::userHomeDirectory));
  165. #endif
  166. return lastFolderFallback;
  167. }
  168. static void displayFailedFilesMessage (const StringArray& failedFiles)
  169. {
  170. AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
  171. TRANS("Errors in Creating Project!"),
  172. TRANS("The following files couldn't be written:")
  173. + "\n\n"
  174. + failedFiles.joinIntoString ("\n", 0, 10));
  175. }
  176. template <typename Callback>
  177. static void prepareDirectory (const File& targetFolder, Callback&& callback)
  178. {
  179. StringArray failedFiles;
  180. if (! targetFolder.exists())
  181. {
  182. if (! targetFolder.createDirectory())
  183. {
  184. displayFailedFilesMessage ({ targetFolder.getFullPathName() });
  185. return;
  186. }
  187. }
  188. else if (FileHelpers::containsAnyNonHiddenFiles (targetFolder))
  189. {
  190. AlertWindow::showOkCancelBox (MessageBoxIconType::InfoIcon,
  191. TRANS("New JUCE Project"),
  192. TRANS("You chose the folder:\n\nXFLDRX\n\n").replace ("XFLDRX", targetFolder.getFullPathName())
  193. + TRANS("This folder isn't empty - are you sure you want to create the project there?")
  194. + "\n\n"
  195. + TRANS("Any existing files with the same names may be overwritten by the new files."),
  196. {},
  197. {},
  198. nullptr,
  199. ModalCallbackFunction::create ([callback] (int result)
  200. {
  201. if (result != 0)
  202. callback();
  203. }));
  204. return;
  205. }
  206. callback();
  207. }
  208. void NewProjectWizard::createNewProject (const NewProjectTemplates::ProjectTemplate& projectTemplate,
  209. const File& targetFolder, const String& name, var modules, var exporters, var fileOptions,
  210. const String& modulePath, bool useGlobalModulePath,
  211. std::function<void (std::unique_ptr<Project>)> callback)
  212. {
  213. prepareDirectory (targetFolder, [=]
  214. {
  215. auto project = std::make_unique<Project> (targetFolder.getChildFile (File::createLegalFileName (name))
  216. .withFileExtension (Project::projectFileExtension));
  217. doBasicProjectSetup (*project, projectTemplate, name);
  218. StringArray failedFiles;
  219. if (addFiles (*project, projectTemplate, name, fileOptions, failedFiles))
  220. {
  221. addExporters (*project, *exporters.getArray());
  222. addModules (*project, *modules.getArray(), modulePath, useGlobalModulePath);
  223. auto sharedProject = std::make_shared<std::unique_ptr<Project>> (std::move (project));
  224. (*sharedProject)->saveAsync (false, true, [sharedProject, failedFiles, callback] (FileBasedDocument::SaveResult r)
  225. {
  226. auto uniqueProject = std::move (*sharedProject.get());
  227. if (r == FileBasedDocument::savedOk)
  228. {
  229. uniqueProject->setChangedFlag (false);
  230. uniqueProject->loadFrom (uniqueProject->getFile(), true);
  231. callback (std::move (uniqueProject));
  232. return;
  233. }
  234. auto failedFilesCopy = failedFiles;
  235. failedFilesCopy.add (uniqueProject->getFile().getFullPathName());
  236. displayFailedFilesMessage (failedFilesCopy);
  237. });
  238. return;
  239. }
  240. displayFailedFilesMessage (failedFiles);
  241. });
  242. }