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.

638 lines
26KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2013 - Raw Material Software Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. #include "jucer_NewProjectWizard.h"
  18. #include "jucer_ProjectType.h"
  19. #include "jucer_Module.h"
  20. #include "../Project Saving/jucer_ProjectExporter.h"
  21. #include "../Application/jucer_Application.h"
  22. #include "../Application/jucer_MainWindow.h"
  23. struct NewProjectWizardClasses
  24. {
  25. //==============================================================================
  26. static void createFileCreationOptionComboBox (Component& setupComp,
  27. OwnedArray<Component>& itemsCreated,
  28. const StringArray& fileOptions)
  29. {
  30. ComboBox* c = new ComboBox();
  31. itemsCreated.add (c);
  32. setupComp.addChildAndSetID (c, "filesToCreate");
  33. c->addItemList (fileOptions, 1);
  34. c->setSelectedId (1, dontSendNotification);
  35. Label* l = new Label (String::empty, TRANS("Files to Auto-Generate") + ":");
  36. l->attachToComponent (c, true);
  37. itemsCreated.add (l);
  38. c->setBounds ("parent.width / 2 + 160, 10, parent.width - 10, top + 22");
  39. }
  40. static int getFileCreationComboResult (Component& setupComp)
  41. {
  42. if (ComboBox* cb = dynamic_cast<ComboBox*> (setupComp.findChildWithID ("filesToCreate")))
  43. return cb->getSelectedItemIndex();
  44. jassertfalse;
  45. return 0;
  46. }
  47. static void setExecutableNameForAllTargets (Project& project, const String& exeName)
  48. {
  49. for (Project::ExporterIterator exporter (project); exporter.next();)
  50. for (ProjectExporter::ConfigIterator config (*exporter); config.next();)
  51. config->getTargetBinaryName() = exeName;
  52. }
  53. static Project::Item createSourceGroup (Project& project)
  54. {
  55. return project.getMainGroup().addNewSubGroup ("Source", 0);
  56. }
  57. static File& getLastWizardFolder()
  58. {
  59. #if JUCE_WINDOWS
  60. static File lastFolder (File::getSpecialLocation (File::userDocumentsDirectory));
  61. #else
  62. static File lastFolder (File::getSpecialLocation (File::userHomeDirectory));
  63. #endif
  64. return lastFolder;
  65. }
  66. //==============================================================================
  67. struct NewProjectWizard
  68. {
  69. NewProjectWizard() {}
  70. virtual ~NewProjectWizard() {}
  71. //==============================================================================
  72. virtual String getName() = 0;
  73. virtual String getDescription() = 0;
  74. virtual void addSetupItems (Component&, OwnedArray<Component>&) {}
  75. virtual Result processResultsFromSetupItems (Component&) { return Result::ok(); }
  76. virtual bool initialiseProject (Project& project) = 0;
  77. String appTitle;
  78. File targetFolder, projectFile;
  79. Component* ownerWindow;
  80. StringArray failedFiles;
  81. //==============================================================================
  82. Project* runWizard (Component* window,
  83. const String& projectName,
  84. const File& target)
  85. {
  86. ownerWindow = window;
  87. appTitle = projectName;
  88. targetFolder = target;
  89. if (! targetFolder.exists())
  90. {
  91. if (! targetFolder.createDirectory())
  92. failedFiles.add (targetFolder.getFullPathName());
  93. }
  94. else if (FileHelpers::containsAnyNonHiddenFiles (targetFolder))
  95. {
  96. if (! AlertWindow::showOkCancelBox (AlertWindow::InfoIcon,
  97. TRANS("New Juce Project"),
  98. TRANS("The folder you chose isn't empty - are you sure you want to create the project there?")
  99. + "\n\n"
  100. + TRANS("Any existing files with the same names may be overwritten by the new files.")))
  101. return nullptr;
  102. }
  103. projectFile = targetFolder.getChildFile (File::createLegalFileName (appTitle))
  104. .withFileExtension (Project::projectFileExtension);
  105. ScopedPointer<Project> project (new Project (projectFile));
  106. project->addDefaultModules (true);
  107. if (failedFiles.size() == 0)
  108. {
  109. project->setFile (projectFile);
  110. project->setTitle (appTitle);
  111. project->getBundleIdentifier() = project->getDefaultBundleIdentifier();
  112. if (! initialiseProject (*project))
  113. return nullptr;
  114. if (project->save (false, true) != FileBasedDocument::savedOk)
  115. return nullptr;
  116. project->setChangedFlag (false);
  117. }
  118. if (failedFiles.size() > 0)
  119. {
  120. AlertWindow::showMessageBox (AlertWindow::WarningIcon,
  121. TRANS("Errors in Creating Project!"),
  122. TRANS("The following files couldn't be written:")
  123. + "\n\n"
  124. + failedFiles.joinIntoString ("\n", 0, 10));
  125. return nullptr;
  126. }
  127. return project.release();
  128. }
  129. //==============================================================================
  130. File getSourceFilesFolder() const
  131. {
  132. return projectFile.getSiblingFile ("Source");
  133. }
  134. void createSourceFolder()
  135. {
  136. if (! getSourceFilesFolder().createDirectory())
  137. failedFiles.add (getSourceFilesFolder().getFullPathName());
  138. }
  139. };
  140. //==============================================================================
  141. struct GUIAppWizard : public NewProjectWizard
  142. {
  143. GUIAppWizard() {}
  144. String getName() { return TRANS("GUI Application"); }
  145. String getDescription() { return TRANS("Creates a standard application"); }
  146. void addSetupItems (Component& setupComp, OwnedArray<Component>& itemsCreated)
  147. {
  148. const String fileOptions[] = { TRANS("Create a Main.cpp file"),
  149. TRANS("Create a Main.cpp file and a basic window"),
  150. TRANS("Don't create any files") };
  151. createFileCreationOptionComboBox (setupComp, itemsCreated,
  152. StringArray (fileOptions, numElementsInArray (fileOptions)));
  153. }
  154. Result processResultsFromSetupItems (Component& setupComp)
  155. {
  156. createMainCpp = createWindow = false;
  157. switch (getFileCreationComboResult (setupComp))
  158. {
  159. case 0: createMainCpp = true; break;
  160. case 1: createMainCpp = createWindow = true; break;
  161. case 2: break;
  162. default: jassertfalse; break;
  163. }
  164. return Result::ok();
  165. }
  166. bool initialiseProject (Project& project)
  167. {
  168. createSourceFolder();
  169. File mainCppFile = getSourceFilesFolder().getChildFile ("Main.cpp");
  170. File contentCompCpp = getSourceFilesFolder().getChildFile ("MainComponent.cpp");
  171. File contentCompH = contentCompCpp.withFileExtension (".h");
  172. String contentCompName = "MainContentComponent";
  173. project.getProjectTypeValue() = ProjectType::getGUIAppTypeName();
  174. Project::Item sourceGroup (createSourceGroup (project));
  175. setExecutableNameForAllTargets (project, File::createLegalFileName (appTitle));
  176. String appHeaders (CodeHelpers::createIncludeStatement (project.getAppIncludeFile(), mainCppFile));
  177. if (createWindow)
  178. {
  179. appHeaders << newLine << CodeHelpers::createIncludeStatement (contentCompH, mainCppFile);
  180. String windowH = project.getFileTemplate ("jucer_ContentCompTemplate_h")
  181. .replace ("INCLUDE_JUCE", CodeHelpers::createIncludeStatement (project.getAppIncludeFile(), contentCompH), false)
  182. .replace ("CONTENTCOMPCLASS", contentCompName, false)
  183. .replace ("HEADERGUARD", CodeHelpers::makeHeaderGuardName (contentCompH), false);
  184. String windowCpp = project.getFileTemplate ("jucer_ContentCompTemplate_cpp")
  185. .replace ("INCLUDE_JUCE", CodeHelpers::createIncludeStatement (project.getAppIncludeFile(), contentCompCpp), false)
  186. .replace ("INCLUDE_CORRESPONDING_HEADER", CodeHelpers::createIncludeStatement (contentCompH, contentCompCpp), false)
  187. .replace ("CONTENTCOMPCLASS", contentCompName, false);
  188. if (! FileHelpers::overwriteFileWithNewDataIfDifferent (contentCompH, windowH))
  189. failedFiles.add (contentCompH.getFullPathName());
  190. if (! FileHelpers::overwriteFileWithNewDataIfDifferent (contentCompCpp, windowCpp))
  191. failedFiles.add (contentCompCpp.getFullPathName());
  192. sourceGroup.addFile (contentCompCpp, -1, true);
  193. sourceGroup.addFile (contentCompH, -1, false);
  194. }
  195. if (createMainCpp)
  196. {
  197. String mainCpp = project.getFileTemplate (createWindow ? "jucer_MainTemplate_Window_cpp"
  198. : "jucer_MainTemplate_NoWindow_cpp")
  199. .replace ("APPHEADERS", appHeaders, false)
  200. .replace ("APPCLASSNAME", CodeHelpers::makeValidIdentifier (appTitle + "Application", false, true, false), false)
  201. .replace ("APPNAME", CodeHelpers::addEscapeChars (appTitle), false)
  202. .replace ("CONTENTCOMPCLASS", contentCompName, false)
  203. .replace ("ALLOWMORETHANONEINSTANCE", "true", false);
  204. if (! FileHelpers::overwriteFileWithNewDataIfDifferent (mainCppFile, mainCpp))
  205. failedFiles.add (mainCppFile.getFullPathName());
  206. sourceGroup.addFile (mainCppFile, -1, true);
  207. }
  208. project.createExporterForCurrentPlatform();
  209. return true;
  210. }
  211. private:
  212. bool createMainCpp, createWindow;
  213. };
  214. //==============================================================================
  215. struct ConsoleAppWizard : public NewProjectWizard
  216. {
  217. ConsoleAppWizard() {}
  218. String getName() { return TRANS("Console Application"); }
  219. String getDescription() { return TRANS("Creates a command-line application with no GUI features"); }
  220. void addSetupItems (Component& setupComp, OwnedArray<Component>& itemsCreated)
  221. {
  222. const String fileOptions[] = { TRANS("Create a Main.cpp file"),
  223. TRANS("Don't create any files") };
  224. createFileCreationOptionComboBox (setupComp, itemsCreated,
  225. StringArray (fileOptions, numElementsInArray (fileOptions)));
  226. }
  227. Result processResultsFromSetupItems (Component& setupComp)
  228. {
  229. createMainCpp = false;
  230. switch (getFileCreationComboResult (setupComp))
  231. {
  232. case 0: createMainCpp = true; break;
  233. case 1: break;
  234. default: jassertfalse; break;
  235. }
  236. return Result::ok();
  237. }
  238. bool initialiseProject (Project& project)
  239. {
  240. createSourceFolder();
  241. project.getProjectTypeValue() = ProjectType::getConsoleAppTypeName();
  242. Project::Item sourceGroup (createSourceGroup (project));
  243. setExecutableNameForAllTargets (project, File::createLegalFileName (appTitle));
  244. if (createMainCpp)
  245. {
  246. File mainCppFile = getSourceFilesFolder().getChildFile ("Main.cpp");
  247. String appHeaders (CodeHelpers::createIncludeStatement (project.getAppIncludeFile(), mainCppFile));
  248. String mainCpp = project.getFileTemplate ("jucer_MainConsoleAppTemplate_cpp")
  249. .replace ("APPHEADERS", appHeaders, false);
  250. if (! FileHelpers::overwriteFileWithNewDataIfDifferent (mainCppFile, mainCpp))
  251. failedFiles.add (mainCppFile.getFullPathName());
  252. sourceGroup.addFile (mainCppFile, -1, true);
  253. }
  254. project.createExporterForCurrentPlatform();
  255. return true;
  256. }
  257. private:
  258. bool createMainCpp;
  259. };
  260. //==============================================================================
  261. struct AudioPluginAppWizard : public NewProjectWizard
  262. {
  263. AudioPluginAppWizard() {}
  264. String getName() { return TRANS("Audio Plug-In"); }
  265. String getDescription() { return TRANS("Creates an audio plugin project"); }
  266. bool initialiseProject (Project& project)
  267. {
  268. createSourceFolder();
  269. String filterClassName = CodeHelpers::makeValidIdentifier (appTitle, true, true, false) + "AudioProcessor";
  270. filterClassName = filterClassName.substring (0, 1).toUpperCase() + filterClassName.substring (1);
  271. String editorClassName = filterClassName + "Editor";
  272. File filterCppFile = getSourceFilesFolder().getChildFile ("PluginProcessor.cpp");
  273. File filterHFile = filterCppFile.withFileExtension (".h");
  274. File editorCppFile = getSourceFilesFolder().getChildFile ("PluginEditor.cpp");
  275. File editorHFile = editorCppFile.withFileExtension (".h");
  276. project.getProjectTypeValue() = ProjectType::getAudioPluginTypeName();
  277. project.addModule ("juce_audio_plugin_client", true);
  278. Project::Item sourceGroup (createSourceGroup (project));
  279. project.getConfigFlag ("JUCE_QUICKTIME") = Project::configFlagDisabled; // disabled because it interferes with RTAS build on PC
  280. setExecutableNameForAllTargets (project, File::createLegalFileName (appTitle));
  281. String appHeaders (CodeHelpers::createIncludeStatement (project.getAppIncludeFile(), filterCppFile));
  282. String filterCpp = project.getFileTemplate ("jucer_AudioPluginFilterTemplate_cpp")
  283. .replace ("FILTERHEADERS", CodeHelpers::createIncludeStatement (filterHFile, filterCppFile)
  284. + newLine + CodeHelpers::createIncludeStatement (editorHFile, filterCppFile), false)
  285. .replace ("FILTERCLASSNAME", filterClassName, false)
  286. .replace ("EDITORCLASSNAME", editorClassName, false);
  287. String filterH = project.getFileTemplate ("jucer_AudioPluginFilterTemplate_h")
  288. .replace ("APPHEADERS", appHeaders, false)
  289. .replace ("FILTERCLASSNAME", filterClassName, false)
  290. .replace ("HEADERGUARD", CodeHelpers::makeHeaderGuardName (filterHFile), false);
  291. String editorCpp = project.getFileTemplate ("jucer_AudioPluginEditorTemplate_cpp")
  292. .replace ("EDITORCPPHEADERS", CodeHelpers::createIncludeStatement (filterHFile, filterCppFile)
  293. + newLine + CodeHelpers::createIncludeStatement (editorHFile, filterCppFile), false)
  294. .replace ("FILTERCLASSNAME", filterClassName, false)
  295. .replace ("EDITORCLASSNAME", editorClassName, false);
  296. String editorH = project.getFileTemplate ("jucer_AudioPluginEditorTemplate_h")
  297. .replace ("EDITORHEADERS", appHeaders + newLine + CodeHelpers::createIncludeStatement (filterHFile, filterCppFile), false)
  298. .replace ("FILTERCLASSNAME", filterClassName, false)
  299. .replace ("EDITORCLASSNAME", editorClassName, false)
  300. .replace ("HEADERGUARD", CodeHelpers::makeHeaderGuardName (editorHFile), false);
  301. if (! FileHelpers::overwriteFileWithNewDataIfDifferent (filterCppFile, filterCpp))
  302. failedFiles.add (filterCppFile.getFullPathName());
  303. if (! FileHelpers::overwriteFileWithNewDataIfDifferent (filterHFile, filterH))
  304. failedFiles.add (filterHFile.getFullPathName());
  305. if (! FileHelpers::overwriteFileWithNewDataIfDifferent (editorCppFile, editorCpp))
  306. failedFiles.add (editorCppFile.getFullPathName());
  307. if (! FileHelpers::overwriteFileWithNewDataIfDifferent (editorHFile, editorH))
  308. failedFiles.add (editorHFile.getFullPathName());
  309. sourceGroup.addFile (filterCppFile, -1, true);
  310. sourceGroup.addFile (filterHFile, -1, false);
  311. sourceGroup.addFile (editorCppFile, -1, true);
  312. sourceGroup.addFile (editorHFile, -1, false);
  313. project.createExporterForCurrentPlatform();
  314. return true;
  315. }
  316. };
  317. //==============================================================================
  318. struct StaticLibraryWizard : public NewProjectWizard
  319. {
  320. StaticLibraryWizard() {}
  321. String getName() { return TRANS("Static Library"); }
  322. String getDescription() { return TRANS("Creates a static library"); }
  323. bool initialiseProject (Project& project)
  324. {
  325. createSourceFolder();
  326. project.getProjectTypeValue() = ProjectType::getStaticLibTypeName();
  327. createSourceGroup (project);
  328. setExecutableNameForAllTargets (project, File::createLegalFileName (appTitle));
  329. project.createExporterForCurrentPlatform();
  330. return true;
  331. }
  332. };
  333. //==============================================================================
  334. struct DynamicLibraryWizard : public NewProjectWizard
  335. {
  336. DynamicLibraryWizard() {}
  337. String getName() { return TRANS("Dynamic Library"); }
  338. String getDescription() { return TRANS("Creates a dynamic library"); }
  339. bool initialiseProject (Project& project)
  340. {
  341. createSourceFolder();
  342. project.getProjectTypeValue() = ProjectType::getDynamicLibTypeName();
  343. createSourceGroup (project);
  344. setExecutableNameForAllTargets (project, File::createLegalFileName (appTitle));
  345. project.createExporterForCurrentPlatform();
  346. return true;
  347. }
  348. };
  349. //==============================================================================
  350. class WizardComp : public Component,
  351. private ButtonListener,
  352. private ComboBoxListener,
  353. private TextEditorListener
  354. {
  355. public:
  356. WizardComp()
  357. : projectName (TRANS("Project name")),
  358. nameLabel (String::empty, TRANS("Project Name") + ":"),
  359. typeLabel (String::empty, TRANS("Project Type") + ":"),
  360. fileBrowser (FileBrowserComponent::saveMode | FileBrowserComponent::canSelectDirectories,
  361. getLastWizardFolder(), nullptr, nullptr),
  362. fileOutline (String::empty, TRANS("Project Folder") + ":"),
  363. createButton (TRANS("Create") + "..."),
  364. cancelButton (TRANS("Cancel"))
  365. {
  366. setOpaque (true);
  367. setSize (600, 500);
  368. addChildAndSetID (&projectName, "projectName");
  369. projectName.setText ("NewProject");
  370. projectName.setBounds ("100, 14, parent.width / 2 - 10, top + 22");
  371. nameLabel.attachToComponent (&projectName, true);
  372. projectName.addListener (this);
  373. addChildAndSetID (&projectType, "projectType");
  374. projectType.addItemList (getWizardNames(), 1);
  375. projectType.setSelectedId (1, dontSendNotification);
  376. projectType.setBounds ("100, projectName.bottom + 4, projectName.right, top + 22");
  377. typeLabel.attachToComponent (&projectType, true);
  378. projectType.addListener (this);
  379. addChildAndSetID (&fileOutline, "fileOutline");
  380. fileOutline.setColour (GroupComponent::outlineColourId, Colours::black.withAlpha (0.2f));
  381. fileOutline.setTextLabelPosition (Justification::centred);
  382. fileOutline.setBounds ("10, projectType.bottom + 20, projectType.right, parent.height - 10");
  383. addChildAndSetID (&fileBrowser, "fileBrowser");
  384. fileBrowser.setBounds ("fileOutline.left + 10, fileOutline.top + 20, fileOutline.right - 10, fileOutline.bottom - 12");
  385. fileBrowser.setFilenameBoxLabel ("Folder:");
  386. addChildAndSetID (&createButton, "createButton");
  387. createButton.setBounds ("right - 140, bottom - 24, parent.width - 10, parent.height - 10");
  388. createButton.addListener (this);
  389. addChildAndSetID (&cancelButton, "cancelButton");
  390. cancelButton.addShortcut (KeyPress (KeyPress::escapeKey));
  391. cancelButton.setBounds ("right - 140, createButton.top, createButton.left - 10, createButton.bottom");
  392. cancelButton.addListener (this);
  393. updateCustomItems();
  394. updateCreateButton();
  395. }
  396. void paint (Graphics& g)
  397. {
  398. g.fillAll (Colour::greyLevel (0.93f));
  399. }
  400. void buttonClicked (Button* b)
  401. {
  402. if (b == &createButton)
  403. {
  404. createProject();
  405. }
  406. else
  407. {
  408. if (MainWindow* mw = dynamic_cast<MainWindow*> (getTopLevelComponent()))
  409. IntrojucerApp::getApp().mainWindowList.closeWindow (mw);
  410. }
  411. }
  412. void createProject()
  413. {
  414. MainWindow* mw = Component::findParentComponentOfClass<MainWindow>();
  415. jassert (mw != nullptr);
  416. ScopedPointer <NewProjectWizard> wizard (createWizard());
  417. if (wizard != nullptr)
  418. {
  419. Result result (wizard->processResultsFromSetupItems (*this));
  420. if (result.failed())
  421. {
  422. AlertWindow::showMessageBox (AlertWindow::WarningIcon,
  423. TRANS("Create Project"),
  424. result.getErrorMessage());
  425. return;
  426. }
  427. ScopedPointer<Project> project (wizard->runWizard (mw, projectName.getText(),
  428. fileBrowser.getSelectedFile (0)));
  429. if (project != nullptr)
  430. mw->setProject (project.release());
  431. }
  432. }
  433. void updateCustomItems()
  434. {
  435. customItems.clear();
  436. ScopedPointer <NewProjectWizard> wizard (createWizard());
  437. if (wizard != nullptr)
  438. wizard->addSetupItems (*this, customItems);
  439. }
  440. void comboBoxChanged (ComboBox*)
  441. {
  442. updateCustomItems();
  443. }
  444. void textEditorTextChanged (TextEditor&)
  445. {
  446. updateCreateButton();
  447. fileBrowser.setFileName (File::createLegalFileName (projectName.getText()));
  448. }
  449. private:
  450. ComboBox projectType;
  451. TextEditor projectName;
  452. Label nameLabel, typeLabel;
  453. FileBrowserComponent fileBrowser;
  454. GroupComponent fileOutline;
  455. TextButton createButton, cancelButton;
  456. OwnedArray<Component> customItems;
  457. NewProjectWizard* createWizard()
  458. {
  459. return createWizardType (projectType.getSelectedItemIndex());
  460. }
  461. void updateCreateButton()
  462. {
  463. createButton.setEnabled (projectName.getText().trim().isNotEmpty());
  464. }
  465. };
  466. //==============================================================================
  467. static int getNumWizards()
  468. {
  469. return 5;
  470. }
  471. static NewProjectWizard* createWizardType (int index)
  472. {
  473. switch (index)
  474. {
  475. case 0: return new GUIAppWizard();
  476. case 1: return new ConsoleAppWizard();
  477. case 2: return new AudioPluginAppWizard();
  478. case 3: return new StaticLibraryWizard();
  479. case 4: return new DynamicLibraryWizard();
  480. default: jassertfalse; break;
  481. }
  482. return 0;
  483. }
  484. static StringArray getWizardNames()
  485. {
  486. StringArray s;
  487. for (int i = 0; i < getNumWizards(); ++i)
  488. {
  489. ScopedPointer <NewProjectWizard> wiz (createWizardType (i));
  490. s.add (wiz->getName());
  491. }
  492. return s;
  493. }
  494. };
  495. Component* createNewProjectWizardComponent()
  496. {
  497. return new NewProjectWizardClasses::WizardComp();
  498. }