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