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.

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