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.

639 lines
27KB

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