/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-11 by Raw Material Software Ltd. ------------------------------------------------------------------------------ JUCE can be redistributed and/or modified under the terms of the GNU General Public License (Version 2), as published by the Free Software Foundation. A copy of the license is included in the JUCE distribution, or can be found online at www.gnu.org/licenses. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.rawmaterialsoftware.com/juce for more information. ============================================================================== */ #include "jucer_NewProjectWizard.h" #include "jucer_ProjectType.h" #include "jucer_Module.h" #include "../Project Saving/jucer_ProjectExporter.h" #include "../Application/jucer_Application.h" #include "../Application/jucer_MainWindow.h" static void createFileCreationOptionComboBox (Component& setupComp, OwnedArray& itemsCreated, const char** types) { ComboBox* c = new ComboBox(); itemsCreated.add (c); setupComp.addChildAndSetID (c, "filesToCreate"); const char* fileOptions[] = { "Create a Main.cpp file", "Create a Main.cpp file and a basic window", "Don't create any files", 0 }; c->addItemList (StringArray (fileOptions), 1); c->setSelectedId (1, false); Label* l = new Label (String::empty, "Files to Auto-Generate:"); l->attachToComponent (c, true); itemsCreated.add (l); c->setBounds ("parent.width / 2 + 160, 10, parent.width - 10, top + 22"); } static void setExecutableNameForAllTargets (Project& project, const String& exeName) { for (Project::ExporterIterator exporter (project); exporter.next();) for (ProjectExporter::ConfigIterator config (*exporter); config.next();) config->getTargetBinaryName() = exeName; } //============================================================================== class GUIAppWizard : public NewProjectWizard { public: GUIAppWizard() {} String getName() { return "GUI Application"; } String getDescription() { return "Creates a standard application"; } void addSetupItems (Component& setupComp, OwnedArray& itemsCreated) { const char* fileOptions[] = { "Create a Main.cpp file", "Create a Main.cpp file and a basic window", "Don't create any files", 0 }; createFileCreationOptionComboBox (setupComp, itemsCreated, fileOptions); } Result processResultsFromSetupItems (Component& setupComp) { ComboBox* cb = dynamic_cast (setupComp.findChildWithID ("filesToCreate")); jassert (cb != nullptr); createMainCpp = createWindow = false; switch (cb->getSelectedItemIndex()) { case 0: createMainCpp = true; break; case 1: createMainCpp = createWindow = true; break; case 2: break; default: jassertfalse; break; } return Result::ok(); } bool initialiseProject (Project& project) { if (! getSourceFilesFolder().createDirectory()) failedFiles.add (getSourceFilesFolder().getFullPathName()); File mainCppFile = getSourceFilesFolder().getChildFile ("Main.cpp"); File mainWindowCpp = getSourceFilesFolder().getChildFile ("MainWindow.cpp"); File mainWindowH = mainWindowCpp.withFileExtension (".h"); String windowClassName = "MainAppWindow"; project.getProjectTypeValue() = ProjectType::getGUIAppTypeName(); Project::Item sourceGroup (project.getMainGroup().addNewSubGroup ("Source", 0)); setExecutableNameForAllTargets (project, File::createLegalFileName (appTitle)); String appHeaders (CodeHelpers::createIncludeStatement (project.getAppIncludeFile(), mainCppFile)); String initCode, shutdownCode, anotherInstanceStartedCode, privateMembers, memberInitialisers; if (createWindow) { appHeaders << newLine << CodeHelpers::createIncludeStatement (mainWindowH, mainCppFile); initCode = "mainWindow = new " + windowClassName + "();"; shutdownCode = "mainWindow = 0;"; privateMembers = "ScopedPointer <" + windowClassName + "> mainWindow;"; String windowH = project.getFileTemplate ("jucer_WindowTemplate_h") .replace ("INCLUDES", CodeHelpers::createIncludeStatement (project.getAppIncludeFile(), mainWindowH), false) .replace ("WINDOWCLASS", windowClassName, false) .replace ("HEADERGUARD", CodeHelpers::makeHeaderGuardName (mainWindowH), false); String windowCpp = project.getFileTemplate ("jucer_WindowTemplate_cpp") .replace ("INCLUDES", CodeHelpers::createIncludeStatement (mainWindowH, mainWindowCpp), false) .replace ("WINDOWCLASS", windowClassName, false); if (! FileHelpers::overwriteFileWithNewDataIfDifferent (mainWindowH, windowH)) failedFiles.add (mainWindowH.getFullPathName()); if (! FileHelpers::overwriteFileWithNewDataIfDifferent (mainWindowCpp, windowCpp)) failedFiles.add (mainWindowCpp.getFullPathName()); sourceGroup.addFile (mainWindowCpp, -1, true); sourceGroup.addFile (mainWindowH, -1, false); } if (createMainCpp) { String mainCpp = project.getFileTemplate ("jucer_MainTemplate_cpp") .replace ("APPHEADERS", appHeaders, false) .replace ("APPCLASSNAME", CodeHelpers::makeValidIdentifier (appTitle + "Application", false, true, false), false) .replace ("MEMBERINITIALISERS", memberInitialisers, false) .replace ("APPINITCODE", initCode, false) .replace ("APPSHUTDOWNCODE", shutdownCode, false) .replace ("APPNAME", CodeHelpers::addEscapeChars (appTitle), false) .replace ("APPVERSION", "1.0", false) .replace ("ALLOWMORETHANONEINSTANCE", "true", false) .replace ("ANOTHERINSTANCECODE", anotherInstanceStartedCode, false) .replace ("PRIVATEMEMBERS", privateMembers, false); if (! FileHelpers::overwriteFileWithNewDataIfDifferent (mainCppFile, mainCpp)) failedFiles.add (mainCppFile.getFullPathName()); sourceGroup.addFile (mainCppFile, -1, true); } return true; } private: bool createMainCpp, createWindow; }; //============================================================================== class ConsoleAppWizard : public NewProjectWizard { public: ConsoleAppWizard() {} String getName() { return "Console Application"; } String getDescription() { return "Creates a command-line application with no GUI features"; } void addSetupItems (Component& setupComp, OwnedArray& itemsCreated) { const char* fileOptions[] = { "Create a Main.cpp file", "Don't create any files", 0 }; createFileCreationOptionComboBox (setupComp, itemsCreated, fileOptions); } Result processResultsFromSetupItems (Component& setupComp) { ComboBox* cb = dynamic_cast (setupComp.findChildWithID ("filesToCreate")); jassert (cb != nullptr); createMainCpp = false; switch (cb->getSelectedItemIndex()) { case 0: createMainCpp = true; break; case 1: break; default: jassertfalse; break; } return Result::ok(); } bool initialiseProject (Project& project) { if (! getSourceFilesFolder().createDirectory()) failedFiles.add (getSourceFilesFolder().getFullPathName()); File mainCppFile = getSourceFilesFolder().getChildFile ("Main.cpp"); project.getProjectTypeValue() = ProjectType::getConsoleAppTypeName(); Project::Item sourceGroup (project.getMainGroup().addNewSubGroup ("Source", 0)); setExecutableNameForAllTargets (project, File::createLegalFileName (appTitle)); if (createMainCpp) { String appHeaders (CodeHelpers::createIncludeStatement (project.getAppIncludeFile(), mainCppFile)); String mainCpp = project.getFileTemplate ("jucer_MainConsoleAppTemplate_cpp") .replace ("APPHEADERS", appHeaders, false); if (! FileHelpers::overwriteFileWithNewDataIfDifferent (mainCppFile, mainCpp)) failedFiles.add (mainCppFile.getFullPathName()); sourceGroup.addFile (mainCppFile, -1, true); } return true; } private: bool createMainCpp; }; //============================================================================== class AudioPluginAppWizard : public NewProjectWizard { public: AudioPluginAppWizard() {} String getName() { return "Audio Plug-In"; } String getDescription() { return "Creates an audio plugin project"; } void addSetupItems (Component& setupComp, OwnedArray& itemsCreated) { } Result processResultsFromSetupItems (Component& setupComp) { return Result::ok(); } bool initialiseProject (Project& project) { if (! getSourceFilesFolder().createDirectory()) failedFiles.add (getSourceFilesFolder().getFullPathName()); String filterClassName = CodeHelpers::makeValidIdentifier (appTitle, true, true, false) + "AudioProcessor"; filterClassName = filterClassName.substring (0, 1).toUpperCase() + filterClassName.substring (1); String editorClassName = filterClassName + "Editor"; File filterCppFile = getSourceFilesFolder().getChildFile ("PluginProcessor.cpp"); File filterHFile = filterCppFile.withFileExtension (".h"); File editorCppFile = getSourceFilesFolder().getChildFile ("PluginEditor.cpp"); File editorHFile = editorCppFile.withFileExtension (".h"); project.getProjectTypeValue() = ProjectType::getAudioPluginTypeName(); project.addModule ("juce_audio_plugin_client", true); Project::Item sourceGroup (project.getMainGroup().addNewSubGroup ("Source", 0)); project.getConfigFlag ("JUCE_QUICKTIME") = Project::configFlagDisabled; // disabled because it interferes with RTAS build on PC setExecutableNameForAllTargets (project, File::createLegalFileName (appTitle)); String appHeaders (CodeHelpers::createIncludeStatement (project.getAppIncludeFile(), filterCppFile)); String filterCpp = project.getFileTemplate ("jucer_AudioPluginFilterTemplate_cpp") .replace ("FILTERHEADERS", CodeHelpers::createIncludeStatement (filterHFile, filterCppFile) + newLine + CodeHelpers::createIncludeStatement (editorHFile, filterCppFile), false) .replace ("FILTERCLASSNAME", filterClassName, false) .replace ("EDITORCLASSNAME", editorClassName, false); String filterH = project.getFileTemplate ("jucer_AudioPluginFilterTemplate_h") .replace ("APPHEADERS", appHeaders, false) .replace ("FILTERCLASSNAME", filterClassName, false) .replace ("HEADERGUARD", CodeHelpers::makeHeaderGuardName (filterHFile), false); String editorCpp = project.getFileTemplate ("jucer_AudioPluginEditorTemplate_cpp") .replace ("EDITORCPPHEADERS", CodeHelpers::createIncludeStatement (filterHFile, filterCppFile) + newLine + CodeHelpers::createIncludeStatement (editorHFile, filterCppFile), false) .replace ("FILTERCLASSNAME", filterClassName, false) .replace ("EDITORCLASSNAME", editorClassName, false); String editorH = project.getFileTemplate ("jucer_AudioPluginEditorTemplate_h") .replace ("EDITORHEADERS", appHeaders + newLine + CodeHelpers::createIncludeStatement (filterHFile, filterCppFile), false) .replace ("FILTERCLASSNAME", filterClassName, false) .replace ("EDITORCLASSNAME", editorClassName, false) .replace ("HEADERGUARD", CodeHelpers::makeHeaderGuardName (editorHFile), false); if (! FileHelpers::overwriteFileWithNewDataIfDifferent (filterCppFile, filterCpp)) failedFiles.add (filterCppFile.getFullPathName()); if (! FileHelpers::overwriteFileWithNewDataIfDifferent (filterHFile, filterH)) failedFiles.add (filterHFile.getFullPathName()); if (! FileHelpers::overwriteFileWithNewDataIfDifferent (editorCppFile, editorCpp)) failedFiles.add (editorCppFile.getFullPathName()); if (! FileHelpers::overwriteFileWithNewDataIfDifferent (editorHFile, editorH)) failedFiles.add (editorHFile.getFullPathName()); sourceGroup.addFile (filterCppFile, -1, true); sourceGroup.addFile (filterHFile, -1, false); sourceGroup.addFile (editorCppFile, -1, true); sourceGroup.addFile (editorHFile, -1, false); return true; } }; //============================================================================== //============================================================================== NewProjectWizard::NewProjectWizard() {} NewProjectWizard::~NewProjectWizard() {} StringArray NewProjectWizard::getWizards() { StringArray s; for (int i = 0; i < getNumWizards(); ++i) { ScopedPointer wiz (createWizard (i)); s.add (wiz->getName()); } return s; } int NewProjectWizard::getNumWizards() { return 3; } NewProjectWizard* NewProjectWizard::createWizard (int index) { switch (index) { case 0: return new GUIAppWizard(); case 1: return new ConsoleAppWizard(); case 2: return new AudioPluginAppWizard(); //case 3: return new BrowserPluginAppWizard(); default: jassertfalse; break; } return 0; } File& NewProjectWizard::getLastWizardFolder() { #if JUCE_WINDOWS static File lastFolder (File::getSpecialLocation (File::userDocumentsDirectory)); #else static File lastFolder (File::getSpecialLocation (File::userHomeDirectory)); #endif return lastFolder; } //============================================================================== Project* NewProjectWizard::runWizard (Component* ownerWindow_, const String& projectName, const File& targetFolder_) { ownerWindow = ownerWindow_; appTitle = projectName; targetFolder = targetFolder_; if (! targetFolder.exists()) { if (! targetFolder.createDirectory()) failedFiles.add (targetFolder.getFullPathName()); } else if (FileHelpers::containsAnyNonHiddenFiles (targetFolder)) { if (! AlertWindow::showOkCancelBox (AlertWindow::InfoIcon, "New Juce Project", "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.")) return nullptr; } projectFile = targetFolder.getChildFile (File::createLegalFileName (appTitle)) .withFileExtension (Project::projectFileExtension); ScopedPointer project (new Project (projectFile)); project->addDefaultModules (true); if (failedFiles.size() == 0) { project->setFile (projectFile); project->setTitle (appTitle); project->setBundleIdentifierToDefault(); if (! initialiseProject (*project)) return nullptr; if (project->save (false, true) != FileBasedDocument::savedOk) return nullptr; project->setChangedFlag (false); } if (failedFiles.size() > 0) { AlertWindow::showMessageBox (AlertWindow::WarningIcon, "Errors in Creating Project!", "The following files couldn't be written:\n\n" + failedFiles.joinIntoString ("\n", 0, 10)); return nullptr; } return project.release(); } //============================================================================== class NewProjectWizard::WizardComp : public Component, private ButtonListener, private ComboBoxListener, private TextEditorListener { public: WizardComp() : projectName ("Project name"), nameLabel (String::empty, "Project Name:"), typeLabel (String::empty, "Project Type:"), fileBrowser (FileBrowserComponent::saveMode | FileBrowserComponent::canSelectDirectories, getLastWizardFolder(), nullptr, nullptr), fileOutline (String::empty, "Project Folder:"), createButton ("Create..."), cancelButton ("Cancel") { setOpaque (true); setSize (600, 500); addChildAndSetID (&projectName, "projectName"); projectName.setText ("NewProject"); projectName.setBounds ("100, 14, parent.width / 2 - 10, top + 22"); nameLabel.attachToComponent (&projectName, true); projectName.addListener (this); addChildAndSetID (&projectType, "projectType"); projectType.addItemList (getWizards(), 1); projectType.setSelectedId (1, true); projectType.setBounds ("100, projectName.bottom + 4, projectName.right, top + 22"); typeLabel.attachToComponent (&projectType, true); projectType.addListener (this); addChildAndSetID (&fileOutline, "fileOutline"); fileOutline.setColour (GroupComponent::outlineColourId, Colours::black.withAlpha (0.2f)); fileOutline.setTextLabelPosition (Justification::centred); fileOutline.setBounds ("10, projectType.bottom + 20, projectType.right, parent.height - 10"); addChildAndSetID (&fileBrowser, "fileBrowser"); fileBrowser.setBounds ("fileOutline.left + 10, fileOutline.top + 20, fileOutline.right - 10, fileOutline.bottom - 12"); fileBrowser.setFilenameBoxLabel ("Folder:"); addChildAndSetID (&createButton, "createButton"); createButton.setBounds ("right - 140, bottom - 24, parent.width - 10, parent.height - 10"); createButton.addListener (this); addChildAndSetID (&cancelButton, "cancelButton"); cancelButton.setBounds ("right - 140, createButton.top, createButton.left - 10, createButton.bottom"); cancelButton.addListener (this); updateCustomItems(); updateCreateButton(); } void paint (Graphics& g) { g.fillAll (Colour::greyLevel (0.93f)); } void buttonClicked (Button* b) { if (b == &createButton) { createProject(); } else { MainWindow* mw = dynamic_cast (getTopLevelComponent()); jassert (mw != nullptr); JucerApplication::getApp()->closeWindow (mw); } } void createProject() { MainWindow* mw = Component::findParentComponentOfClass(); jassert (mw != nullptr); ScopedPointer wizard (createWizard()); if (wizard != nullptr) { Result result (wizard->processResultsFromSetupItems (*this)); if (result.failed()) { AlertWindow::showMessageBox (AlertWindow::WarningIcon, "Create Project", result.getErrorMessage()); return; } ScopedPointer project (wizard->runWizard (mw, projectName.getText(), fileBrowser.getSelectedFile (0))); if (project != nullptr) mw->setProject (project.release()); } } void updateCustomItems() { customItems.clear(); ScopedPointer wizard (createWizard()); if (wizard != nullptr) wizard->addSetupItems (*this, customItems); } void comboBoxChanged (ComboBox*) { updateCustomItems(); } void textEditorTextChanged (TextEditor&) { updateCreateButton(); fileBrowser.setFileName (File::createLegalFileName (projectName.getText())); } private: ComboBox projectType; TextEditor projectName; Label nameLabel, typeLabel; FileBrowserComponent fileBrowser; GroupComponent fileOutline; TextButton createButton, cancelButton; OwnedArray customItems; NewProjectWizard* createWizard() { return NewProjectWizard::createWizard (projectType.getSelectedItemIndex()); } void updateCreateButton() { createButton.setEnabled (projectName.getText().trim().isNotEmpty()); } }; Component* NewProjectWizard::createComponent() { return new WizardComp(); }