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.

278 lines
11KB

  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, true, 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. for (auto& mod : list.getAllModules())
  142. if (modules.contains (mod.first))
  143. project.getEnabledModules().addModule (mod.second, false, useGlobalPath);
  144. }
  145. static void addExporters (Project& project, Array<var> exporters)
  146. {
  147. for (auto exporter : exporters)
  148. project.addNewExporter (exporter.toString());
  149. for (Project::ExporterIterator exporter (project); exporter.next();)
  150. for (ProjectExporter::ConfigIterator config (*exporter); config.next();)
  151. config->getValue (Ids::targetName) = project.getProjectFilenameRootString();
  152. }
  153. //==============================================================================
  154. File NewProjectWizard::getLastWizardFolder()
  155. {
  156. if (getAppSettings().lastWizardFolder.isDirectory())
  157. return getAppSettings().lastWizardFolder;
  158. #if JUCE_WINDOWS
  159. static File lastFolderFallback (File::getSpecialLocation (File::userDocumentsDirectory));
  160. #else
  161. static File lastFolderFallback (File::getSpecialLocation (File::userHomeDirectory));
  162. #endif
  163. return lastFolderFallback;
  164. }
  165. std::unique_ptr<Project> NewProjectWizard::createNewProject (const NewProjectTemplates::ProjectTemplate& projectTemplate,
  166. const File& targetFolder, const String& name, var modules, var exporters, var fileOptions,
  167. const String& modulePath, bool useGlobalModulePath)
  168. {
  169. StringArray failedFiles;
  170. if (! targetFolder.exists())
  171. {
  172. if (! targetFolder.createDirectory())
  173. failedFiles.add (targetFolder.getFullPathName());
  174. }
  175. else if (FileHelpers::containsAnyNonHiddenFiles (targetFolder))
  176. {
  177. if (! AlertWindow::showOkCancelBox (AlertWindow::InfoIcon,
  178. TRANS("New JUCE Project"),
  179. TRANS("You chose the folder:\n\nXFLDRX\n\n").replace ("XFLDRX", targetFolder.getFullPathName())
  180. + TRANS("This folder isn't empty - are you sure you want to create the project there?")
  181. + "\n\n"
  182. + TRANS("Any existing files with the same names may be overwritten by the new files.")))
  183. {
  184. return nullptr;
  185. }
  186. }
  187. auto project = std::make_unique<Project> (targetFolder.getChildFile (File::createLegalFileName (name))
  188. .withFileExtension (Project::projectFileExtension));
  189. if (failedFiles.isEmpty())
  190. {
  191. doBasicProjectSetup (*project, projectTemplate, name);
  192. if (addFiles (*project, projectTemplate, name, fileOptions, failedFiles))
  193. {
  194. addExporters (*project, *exporters.getArray());
  195. addModules (*project, *modules.getArray(), modulePath, useGlobalModulePath);
  196. if (project->save (false, true) == FileBasedDocument::savedOk)
  197. {
  198. project->setChangedFlag (false);
  199. project->loadFrom (project->getFile(), true);
  200. }
  201. else
  202. {
  203. failedFiles.add (project->getFile().getFullPathName());
  204. }
  205. }
  206. }
  207. if (! failedFiles.isEmpty())
  208. {
  209. AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
  210. TRANS("Errors in Creating Project!"),
  211. TRANS("The following files couldn't be written:")
  212. + "\n\n"
  213. + failedFiles.joinIntoString ("\n", 0, 10));
  214. return nullptr;
  215. }
  216. return project;
  217. }