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.

575 lines
23KB

  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. static void createFileCreationOptionComboBox (Component& setupComp,
  25. OwnedArray<Component>& itemsCreated,
  26. const char** types)
  27. {
  28. ComboBox* c = new ComboBox();
  29. itemsCreated.add (c);
  30. setupComp.addChildAndSetID (c, "filesToCreate");
  31. const char* fileOptions[] = { "Create a Main.cpp file",
  32. "Create a Main.cpp file and a basic window",
  33. "Don't create any files", 0 };
  34. c->addItemList (StringArray (fileOptions), 1);
  35. c->setSelectedId (1, false);
  36. Label* l = new Label (String::empty, "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 void setExecutableNameForAllTargets (Project& project, const String& exeName)
  42. {
  43. for (int j = project.getNumExporters(); --j >= 0;)
  44. {
  45. ScopedPointer<ProjectExporter> exporter (project.createExporter(j));
  46. if (exporter != nullptr)
  47. {
  48. for (int i = exporter->getNumConfigurations(); --i >= 0;)
  49. exporter->getConfiguration(i)->getTargetBinaryName() = exeName;
  50. }
  51. }
  52. }
  53. //==============================================================================
  54. class GUIAppWizard : public NewProjectWizard
  55. {
  56. public:
  57. GUIAppWizard() {}
  58. String getName() { return "GUI Application"; }
  59. String getDescription() { return "Creates a standard application"; }
  60. void addSetupItems (Component& setupComp, OwnedArray<Component>& itemsCreated)
  61. {
  62. const char* fileOptions[] = { "Create a Main.cpp file",
  63. "Create a Main.cpp file and a basic window",
  64. "Don't create any files", 0 };
  65. createFileCreationOptionComboBox (setupComp, itemsCreated, fileOptions);
  66. }
  67. Result processResultsFromSetupItems (Component& setupComp)
  68. {
  69. ComboBox* cb = dynamic_cast<ComboBox*> (setupComp.findChildWithID ("filesToCreate"));
  70. jassert (cb != nullptr);
  71. createMainCpp = createWindow = false;
  72. switch (cb->getSelectedItemIndex())
  73. {
  74. case 0: createMainCpp = true; break;
  75. case 1: createMainCpp = createWindow = true; break;
  76. case 2: break;
  77. default: jassertfalse; break;
  78. }
  79. return Result::ok();
  80. }
  81. bool initialiseProject (Project& project)
  82. {
  83. if (! getSourceFilesFolder().createDirectory())
  84. failedFiles.add (getSourceFilesFolder().getFullPathName());
  85. File mainCppFile = getSourceFilesFolder().getChildFile ("Main.cpp");
  86. File mainWindowCpp = getSourceFilesFolder().getChildFile ("MainWindow.cpp");
  87. File mainWindowH = mainWindowCpp.withFileExtension (".h");
  88. String windowClassName = "MainAppWindow";
  89. project.getProjectTypeValue() = ProjectType::getGUIAppTypeName();
  90. Project::Item sourceGroup (project.getMainGroup().addNewSubGroup ("Source", 0));
  91. setExecutableNameForAllTargets (project, File::createLegalFileName (appTitle));
  92. String appHeaders (CodeHelpers::createIncludeStatement (project.getAppIncludeFile(), mainCppFile));
  93. String initCode, shutdownCode, anotherInstanceStartedCode, privateMembers, memberInitialisers;
  94. if (createWindow)
  95. {
  96. appHeaders << newLine << CodeHelpers::createIncludeStatement (mainWindowH, mainCppFile);
  97. initCode = "mainWindow = new " + windowClassName + "();";
  98. shutdownCode = "mainWindow = 0;";
  99. privateMembers = "ScopedPointer <" + windowClassName + "> mainWindow;";
  100. String windowH = project.getFileTemplate ("jucer_WindowTemplate_h")
  101. .replace ("INCLUDES", CodeHelpers::createIncludeStatement (project.getAppIncludeFile(), mainWindowH), false)
  102. .replace ("WINDOWCLASS", windowClassName, false)
  103. .replace ("HEADERGUARD", CodeHelpers::makeHeaderGuardName (mainWindowH), false);
  104. String windowCpp = project.getFileTemplate ("jucer_WindowTemplate_cpp")
  105. .replace ("INCLUDES", CodeHelpers::createIncludeStatement (mainWindowH, mainWindowCpp), false)
  106. .replace ("WINDOWCLASS", windowClassName, false);
  107. if (! FileHelpers::overwriteFileWithNewDataIfDifferent (mainWindowH, windowH))
  108. failedFiles.add (mainWindowH.getFullPathName());
  109. if (! FileHelpers::overwriteFileWithNewDataIfDifferent (mainWindowCpp, windowCpp))
  110. failedFiles.add (mainWindowCpp.getFullPathName());
  111. sourceGroup.addFile (mainWindowCpp, -1, true);
  112. sourceGroup.addFile (mainWindowH, -1, false);
  113. }
  114. if (createMainCpp)
  115. {
  116. String mainCpp = project.getFileTemplate ("jucer_MainTemplate_cpp")
  117. .replace ("APPHEADERS", appHeaders, false)
  118. .replace ("APPCLASSNAME", CodeHelpers::makeValidIdentifier (appTitle + "Application", false, true, false), false)
  119. .replace ("MEMBERINITIALISERS", memberInitialisers, false)
  120. .replace ("APPINITCODE", initCode, false)
  121. .replace ("APPSHUTDOWNCODE", shutdownCode, false)
  122. .replace ("APPNAME", CodeHelpers::addEscapeChars (appTitle), false)
  123. .replace ("APPVERSION", "1.0", false)
  124. .replace ("ALLOWMORETHANONEINSTANCE", "true", false)
  125. .replace ("ANOTHERINSTANCECODE", anotherInstanceStartedCode, false)
  126. .replace ("PRIVATEMEMBERS", privateMembers, false);
  127. if (! FileHelpers::overwriteFileWithNewDataIfDifferent (mainCppFile, mainCpp))
  128. failedFiles.add (mainCppFile.getFullPathName());
  129. sourceGroup.addFile (mainCppFile, -1, true);
  130. }
  131. return true;
  132. }
  133. private:
  134. bool createMainCpp, createWindow;
  135. };
  136. //==============================================================================
  137. class ConsoleAppWizard : public NewProjectWizard
  138. {
  139. public:
  140. ConsoleAppWizard() {}
  141. String getName() { return "Console Application"; }
  142. String getDescription() { return "Creates a command-line application with no GUI features"; }
  143. void addSetupItems (Component& setupComp, OwnedArray<Component>& itemsCreated)
  144. {
  145. const char* fileOptions[] = { "Create a Main.cpp file",
  146. "Don't create any files", 0 };
  147. createFileCreationOptionComboBox (setupComp, itemsCreated, fileOptions);
  148. }
  149. Result processResultsFromSetupItems (Component& setupComp)
  150. {
  151. ComboBox* cb = dynamic_cast<ComboBox*> (setupComp.findChildWithID ("filesToCreate"));
  152. jassert (cb != nullptr);
  153. createMainCpp = false;
  154. switch (cb->getSelectedItemIndex())
  155. {
  156. case 0: createMainCpp = true; break;
  157. case 1: break;
  158. default: jassertfalse; break;
  159. }
  160. return Result::ok();
  161. }
  162. bool initialiseProject (Project& project)
  163. {
  164. if (! getSourceFilesFolder().createDirectory())
  165. failedFiles.add (getSourceFilesFolder().getFullPathName());
  166. File mainCppFile = getSourceFilesFolder().getChildFile ("Main.cpp");
  167. project.getProjectTypeValue() = ProjectType::getConsoleAppTypeName();
  168. Project::Item sourceGroup (project.getMainGroup().addNewSubGroup ("Source", 0));
  169. setExecutableNameForAllTargets (project, File::createLegalFileName (appTitle));
  170. if (createMainCpp)
  171. {
  172. String appHeaders (CodeHelpers::createIncludeStatement (project.getAppIncludeFile(), mainCppFile));
  173. String mainCpp = project.getFileTemplate ("jucer_MainConsoleAppTemplate_cpp")
  174. .replace ("APPHEADERS", appHeaders, false);
  175. if (! FileHelpers::overwriteFileWithNewDataIfDifferent (mainCppFile, mainCpp))
  176. failedFiles.add (mainCppFile.getFullPathName());
  177. sourceGroup.addFile (mainCppFile, -1, true);
  178. }
  179. return true;
  180. }
  181. private:
  182. bool createMainCpp;
  183. };
  184. //==============================================================================
  185. class AudioPluginAppWizard : public NewProjectWizard
  186. {
  187. public:
  188. AudioPluginAppWizard() {}
  189. String getName() { return "Audio Plug-In"; }
  190. String getDescription() { return "Creates an audio plugin project"; }
  191. void addSetupItems (Component& setupComp, OwnedArray<Component>& itemsCreated)
  192. {
  193. }
  194. Result processResultsFromSetupItems (Component& setupComp)
  195. {
  196. return Result::ok();
  197. }
  198. bool initialiseProject (Project& project)
  199. {
  200. if (! getSourceFilesFolder().createDirectory())
  201. failedFiles.add (getSourceFilesFolder().getFullPathName());
  202. String filterClassName = CodeHelpers::makeValidIdentifier (appTitle, true, true, false) + "AudioProcessor";
  203. filterClassName = filterClassName.substring (0, 1).toUpperCase() + filterClassName.substring (1);
  204. String editorClassName = filterClassName + "Editor";
  205. File filterCppFile = getSourceFilesFolder().getChildFile ("PluginProcessor.cpp");
  206. File filterHFile = filterCppFile.withFileExtension (".h");
  207. File editorCppFile = getSourceFilesFolder().getChildFile ("PluginEditor.cpp");
  208. File editorHFile = editorCppFile.withFileExtension (".h");
  209. project.getProjectTypeValue() = ProjectType::getAudioPluginTypeName();
  210. project.addModule ("juce_audio_plugin_client", true);
  211. Project::Item sourceGroup (project.getMainGroup().addNewSubGroup ("Source", 0));
  212. project.getConfigFlag ("JUCE_QUICKTIME") = Project::configFlagDisabled; // disabled because it interferes with RTAS build on PC
  213. setExecutableNameForAllTargets (project, File::createLegalFileName (appTitle));
  214. String appHeaders (CodeHelpers::createIncludeStatement (project.getAppIncludeFile(), filterCppFile));
  215. String filterCpp = project.getFileTemplate ("jucer_AudioPluginFilterTemplate_cpp")
  216. .replace ("FILTERHEADERS", CodeHelpers::createIncludeStatement (filterHFile, filterCppFile)
  217. + newLine + CodeHelpers::createIncludeStatement (editorHFile, filterCppFile), false)
  218. .replace ("FILTERCLASSNAME", filterClassName, false)
  219. .replace ("EDITORCLASSNAME", editorClassName, false);
  220. String filterH = project.getFileTemplate ("jucer_AudioPluginFilterTemplate_h")
  221. .replace ("APPHEADERS", appHeaders, false)
  222. .replace ("FILTERCLASSNAME", filterClassName, false)
  223. .replace ("HEADERGUARD", CodeHelpers::makeHeaderGuardName (filterHFile), false);
  224. String editorCpp = project.getFileTemplate ("jucer_AudioPluginEditorTemplate_cpp")
  225. .replace ("EDITORCPPHEADERS", CodeHelpers::createIncludeStatement (filterHFile, filterCppFile)
  226. + newLine + CodeHelpers::createIncludeStatement (editorHFile, filterCppFile), false)
  227. .replace ("FILTERCLASSNAME", filterClassName, false)
  228. .replace ("EDITORCLASSNAME", editorClassName, false);
  229. String editorH = project.getFileTemplate ("jucer_AudioPluginEditorTemplate_h")
  230. .replace ("EDITORHEADERS", appHeaders + newLine + CodeHelpers::createIncludeStatement (filterHFile, filterCppFile), false)
  231. .replace ("FILTERCLASSNAME", filterClassName, false)
  232. .replace ("EDITORCLASSNAME", editorClassName, false)
  233. .replace ("HEADERGUARD", CodeHelpers::makeHeaderGuardName (editorHFile), false);
  234. if (! FileHelpers::overwriteFileWithNewDataIfDifferent (filterCppFile, filterCpp))
  235. failedFiles.add (filterCppFile.getFullPathName());
  236. if (! FileHelpers::overwriteFileWithNewDataIfDifferent (filterHFile, filterH))
  237. failedFiles.add (filterHFile.getFullPathName());
  238. if (! FileHelpers::overwriteFileWithNewDataIfDifferent (editorCppFile, editorCpp))
  239. failedFiles.add (editorCppFile.getFullPathName());
  240. if (! FileHelpers::overwriteFileWithNewDataIfDifferent (editorHFile, editorH))
  241. failedFiles.add (editorHFile.getFullPathName());
  242. sourceGroup.addFile (filterCppFile, -1, true);
  243. sourceGroup.addFile (filterHFile, -1, false);
  244. sourceGroup.addFile (editorCppFile, -1, true);
  245. sourceGroup.addFile (editorHFile, -1, false);
  246. return true;
  247. }
  248. };
  249. //==============================================================================
  250. //==============================================================================
  251. NewProjectWizard::NewProjectWizard() {}
  252. NewProjectWizard::~NewProjectWizard() {}
  253. StringArray NewProjectWizard::getWizards()
  254. {
  255. StringArray s;
  256. for (int i = 0; i < getNumWizards(); ++i)
  257. {
  258. ScopedPointer <NewProjectWizard> wiz (createWizard (i));
  259. s.add (wiz->getName());
  260. }
  261. return s;
  262. }
  263. int NewProjectWizard::getNumWizards()
  264. {
  265. return 3;
  266. }
  267. NewProjectWizard* NewProjectWizard::createWizard (int index)
  268. {
  269. switch (index)
  270. {
  271. case 0: return new GUIAppWizard();
  272. case 1: return new ConsoleAppWizard();
  273. case 2: return new AudioPluginAppWizard();
  274. //case 3: return new BrowserPluginAppWizard();
  275. default: jassertfalse; break;
  276. }
  277. return 0;
  278. }
  279. File& NewProjectWizard::getLastWizardFolder()
  280. {
  281. #if JUCE_WINDOWS
  282. static File lastFolder (File::getSpecialLocation (File::userDocumentsDirectory));
  283. #else
  284. static File lastFolder (File::getSpecialLocation (File::userHomeDirectory));
  285. #endif
  286. return lastFolder;
  287. }
  288. //==============================================================================
  289. Project* NewProjectWizard::runWizard (Component* ownerWindow_,
  290. const String& projectName,
  291. const File& targetFolder_)
  292. {
  293. ownerWindow = ownerWindow_;
  294. appTitle = projectName;
  295. targetFolder = targetFolder_;
  296. if (! targetFolder.exists())
  297. {
  298. if (! targetFolder.createDirectory())
  299. failedFiles.add (targetFolder.getFullPathName());
  300. }
  301. else if (FileHelpers::containsAnyNonHiddenFiles (targetFolder))
  302. {
  303. if (! AlertWindow::showOkCancelBox (AlertWindow::InfoIcon, "New Juce Project",
  304. "The folder you chose isn't empty - are you sure you want to create the project there?\n\nAny existing files with the same names may be overwritten by the new files."))
  305. return nullptr;
  306. }
  307. projectFile = targetFolder.getChildFile (File::createLegalFileName (appTitle))
  308. .withFileExtension (Project::projectFileExtension);
  309. ScopedPointer<Project> project (new Project (projectFile));
  310. project->addDefaultModules (true);
  311. if (failedFiles.size() == 0)
  312. {
  313. project->setFile (projectFile);
  314. project->setTitle (appTitle);
  315. project->setBundleIdentifierToDefault();
  316. if (! initialiseProject (*project))
  317. return nullptr;
  318. if (project->save (false, true) != FileBasedDocument::savedOk)
  319. return nullptr;
  320. project->setChangedFlag (false);
  321. }
  322. if (failedFiles.size() > 0)
  323. {
  324. AlertWindow::showMessageBox (AlertWindow::WarningIcon,
  325. "Errors in Creating Project!",
  326. "The following files couldn't be written:\n\n"
  327. + failedFiles.joinIntoString ("\n", 0, 10));
  328. return nullptr;
  329. }
  330. return project.release();
  331. }
  332. //==============================================================================
  333. class NewProjectWizard::WizardComp : public Component,
  334. private ButtonListener,
  335. private ComboBoxListener,
  336. private TextEditorListener
  337. {
  338. public:
  339. WizardComp()
  340. : projectName ("Project name"),
  341. nameLabel (String::empty, "Project Name:"),
  342. typeLabel (String::empty, "Project Type:"),
  343. fileBrowser (FileBrowserComponent::saveMode | FileBrowserComponent::canSelectDirectories,
  344. getLastWizardFolder(), nullptr, nullptr),
  345. fileOutline (String::empty, "Project Folder:"),
  346. createButton ("Create..."),
  347. cancelButton ("Cancel")
  348. {
  349. setOpaque (true);
  350. setSize (600, 500);
  351. addChildAndSetID (&projectName, "projectName");
  352. projectName.setText ("NewProject");
  353. projectName.setBounds ("100, 14, parent.width / 2 - 10, top + 22");
  354. nameLabel.attachToComponent (&projectName, true);
  355. projectName.addListener (this);
  356. addChildAndSetID (&projectType, "projectType");
  357. projectType.addItemList (getWizards(), 1);
  358. projectType.setSelectedId (1, true);
  359. projectType.setBounds ("100, projectName.bottom + 4, projectName.right, top + 22");
  360. typeLabel.attachToComponent (&projectType, true);
  361. projectType.addListener (this);
  362. addChildAndSetID (&fileOutline, "fileOutline");
  363. fileOutline.setColour (GroupComponent::outlineColourId, Colours::black.withAlpha (0.2f));
  364. fileOutline.setTextLabelPosition (Justification::centred);
  365. fileOutline.setBounds ("10, projectType.bottom + 20, projectType.right, parent.height - 10");
  366. addChildAndSetID (&fileBrowser, "fileBrowser");
  367. fileBrowser.setBounds ("fileOutline.left + 10, fileOutline.top + 20, fileOutline.right - 10, fileOutline.bottom - 12");
  368. fileBrowser.setFilenameBoxLabel ("Folder:");
  369. addChildAndSetID (&createButton, "createButton");
  370. createButton.setBounds ("right - 140, bottom - 24, parent.width - 10, parent.height - 10");
  371. createButton.addListener (this);
  372. addChildAndSetID (&cancelButton, "cancelButton");
  373. cancelButton.setBounds ("right - 140, createButton.top, createButton.left - 10, createButton.bottom");
  374. cancelButton.addListener (this);
  375. updateCustomItems();
  376. updateCreateButton();
  377. }
  378. void paint (Graphics& g)
  379. {
  380. g.fillAll (Colour::greyLevel (0.93f));
  381. }
  382. void buttonClicked (Button* b)
  383. {
  384. if (b == &createButton)
  385. {
  386. createProject();
  387. }
  388. else
  389. {
  390. MainWindow* mw = dynamic_cast<MainWindow*> (getTopLevelComponent());
  391. jassert (mw != nullptr);
  392. JucerApplication::getApp()->closeWindow (mw);
  393. }
  394. }
  395. void createProject()
  396. {
  397. MainWindow* mw = Component::findParentComponentOfClass<MainWindow>();
  398. jassert (mw != nullptr);
  399. ScopedPointer <NewProjectWizard> wizard (createWizard());
  400. if (wizard != nullptr)
  401. {
  402. Result result (wizard->processResultsFromSetupItems (*this));
  403. if (result.failed())
  404. {
  405. AlertWindow::showMessageBox (AlertWindow::WarningIcon, "Create Project", result.getErrorMessage());
  406. return;
  407. }
  408. ScopedPointer<Project> project (wizard->runWizard (mw, projectName.getText(),
  409. fileBrowser.getSelectedFile (0)));
  410. if (project != nullptr)
  411. mw->setProject (project.release());
  412. }
  413. }
  414. void updateCustomItems()
  415. {
  416. customItems.clear();
  417. ScopedPointer <NewProjectWizard> wizard (createWizard());
  418. if (wizard != nullptr)
  419. wizard->addSetupItems (*this, customItems);
  420. }
  421. void comboBoxChanged (ComboBox*)
  422. {
  423. updateCustomItems();
  424. }
  425. void textEditorTextChanged (TextEditor&)
  426. {
  427. updateCreateButton();
  428. fileBrowser.setFileName (File::createLegalFileName (projectName.getText()));
  429. }
  430. private:
  431. ComboBox projectType;
  432. TextEditor projectName;
  433. Label nameLabel, typeLabel;
  434. FileBrowserComponent fileBrowser;
  435. GroupComponent fileOutline;
  436. TextButton createButton, cancelButton;
  437. OwnedArray<Component> customItems;
  438. NewProjectWizard* createWizard()
  439. {
  440. return NewProjectWizard::createWizard (projectType.getSelectedItemIndex());
  441. }
  442. void updateCreateButton()
  443. {
  444. createButton.setEnabled (projectName.getText().trim().isNotEmpty());
  445. }
  446. };
  447. Component* NewProjectWizard::createComponent()
  448. {
  449. return new WizardComp();
  450. }