/* ============================================================================== This file is part of the JUCE 6 technical preview. Copyright (c) 2020 - Raw Material Software Limited You may use this code under the terms of the GPL v3 (see www.gnu.org/licenses). For this technical preview, this file is not subject to commercial licensing. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ #pragma once //============================================================================== class ModulesFolderPathBox : public Component { public: ModulesFolderPathBox (String initialFileOrDirectory) : currentPathBox ("currentPathBox"), openFolderButton (TRANS("...")), modulesLabel (String(), TRANS("Modules Folder") + ":"), useGlobalPathsToggle ("Use global module path") { if (initialFileOrDirectory.isEmpty()) initialFileOrDirectory = getAppSettings().getStoredPath (Ids::defaultJuceModulePath, TargetOS::getThisOS()).get().toString(); setModulesFolder (initialFileOrDirectory); addAndMakeVisible (currentPathBox); currentPathBox.setEditableText (true); currentPathBox.onChange = [this] { setModulesFolder (File::getCurrentWorkingDirectory() .getChildFile (currentPathBox.getText())); }; addAndMakeVisible (openFolderButton); openFolderButton.setTooltip (TRANS ("Select JUCE modules folder")); openFolderButton.onClick = [this] { selectJuceFolder(); }; addAndMakeVisible (modulesLabel); modulesLabel.attachToComponent (¤tPathBox, true); auto updateEnablement = [this] { isUsingGlobalPaths = useGlobalPathsToggle.getToggleState(); currentPathBox.setEnabled (! isUsingGlobalPaths); openFolderButton.setEnabled (! isUsingGlobalPaths); modulesLabel.setEnabled (! isUsingGlobalPaths); }; addAndMakeVisible (useGlobalPathsToggle); useGlobalPathsToggle.setToggleState (true, sendNotification); useGlobalPathsToggle.onClick = [updateEnablement] { updateEnablement(); }; updateEnablement(); } void resized() override { auto b = getLocalBounds(); auto topSlice = b.removeFromTop (b.getHeight() / 2); openFolderButton.setBounds (topSlice.removeFromRight (30)); modulesLabel.setBounds (topSlice.removeFromLeft (110)); currentPathBox.setBounds (topSlice); b.removeFromTop (5); useGlobalPathsToggle.setBounds (b.translated (20, 0)); } static bool selectJuceFolder (File& result) { for (;;) { FileChooser fc ("Select your JUCE modules folder...", { getAppSettings().getStoredPath (Ids::defaultJuceModulePath, TargetOS::getThisOS()).get().toString() }, "*"); if (! fc.browseForDirectory()) return false; if (isJUCEModulesFolder (fc.getResult())) { result = fc.getResult(); return true; } AlertWindow::showMessageBox (AlertWindow::WarningIcon, "Not a valid JUCE modules folder!", "Please select the folder containing your juce_* modules!\n\n" "This is required so that the new project can be given some essential core modules."); } } void selectJuceFolder() { File result; if (selectJuceFolder (result)) setModulesFolder (result); } void setModulesFolder (const File& newFolder) { if (modulesFolder != newFolder) { modulesFolder = newFolder; currentPathBox.setText (modulesFolder.getFullPathName(), dontSendNotification); } } File modulesFolder; bool isUsingGlobalPaths = true; private: ComboBox currentPathBox; TextButton openFolderButton; Label modulesLabel; ToggleButton useGlobalPathsToggle; }; /** The target platforms chooser for the chosen template. */ class PlatformTargetsComp : public Component, private ListBoxModel { public: PlatformTargetsComp() { setOpaque (false); const Array types (ProjectExporter::getExporterTypes()); for (auto& type : types) { platforms.add (new PlatformType { type.getIcon(), type.name }); addAndMakeVisible (toggles.add (new ToggleButton (String()))); } listBox.setRowHeight (30); listBox.setModel (this); listBox.setOpaque (false); listBox.setMultipleSelectionEnabled (true); listBox.setClickingTogglesRowSelection (true); listBox.setColour (ListBox::backgroundColourId, Colours::transparentBlack); addAndMakeVisible (listBox); selectDefaultExporterIfNoneSelected(); } StringArray getSelectedPlatforms() const { StringArray list; for (int i = 0; i < platforms.size(); ++i) if (listBox.isRowSelected (i)) list.add (platforms.getUnchecked(i)->name); return list; } void selectDefaultExporterIfNoneSelected() { if (listBox.getNumSelectedRows() == 0) { for (int i = platforms.size(); --i >= 0;) { if (platforms.getUnchecked(i)->name == ProjectExporter::getCurrentPlatformExporterName()) { listBox.selectRow (i); break; } } } } void resized() override { listBox.setBounds (getLocalBounds()); } int getNumRows() override { return platforms.size(); } void paintListBoxItem (int rowNumber, Graphics& g, int width, int height, bool rowIsSelected) override { ignoreUnused (width); if (auto* platform = platforms[rowNumber]) { auto bounds = getLocalBounds().withHeight (height).withTrimmedBottom (1); g.setColour (findColour (rowNumber % 2 == 0 ? widgetBackgroundColourId : secondaryWidgetBackgroundColourId)); g.fillRect (bounds); bounds.removeFromLeft (10); auto toggleBounds = bounds.removeFromLeft (height); drawToggle (g, toggleBounds, rowIsSelected); auto iconBounds = bounds.removeFromLeft (height).reduced (5); g.drawImageWithin (platform->icon, iconBounds.getX(), iconBounds.getY(), iconBounds.getWidth(), iconBounds.getHeight(), RectanglePlacement::fillDestination); bounds.removeFromLeft (10); g.setColour (findColour (widgetTextColourId)); g.drawFittedText (platform->name, bounds, Justification::centredLeft, 1); } } void selectedRowsChanged (int) override { selectDefaultExporterIfNoneSelected(); } private: struct PlatformType { Image icon; String name; }; void drawToggle (Graphics& g, Rectangle bounds, bool isToggled) { auto sideLength = jmin (bounds.getWidth(), bounds.getHeight()); bounds = bounds.withSizeKeepingCentre (sideLength, sideLength).reduced (4); g.setColour (findColour (ToggleButton::tickDisabledColourId)); g.drawRoundedRectangle (bounds.toFloat(), 2.0f, 1.0f); if (isToggled) { g.setColour (findColour (ToggleButton::tickColourId)); const auto tick = getTickShape (0.75f); g.fillPath (tick, tick.getTransformToScaleToFit (bounds.reduced (4, 5).toFloat(), false)); } } Path getTickShape (float height) { static const unsigned char pathData[] = { 110,109,32,210,202,64,126,183,148,64,108,39,244,247,64,245,76,124,64,108,178,131,27,65,246,76,252,64,108,175,242,4,65,246,76,252, 64,108,236,5,68,65,0,0,160,180,108,240,150,90,65,21,136,52,63,108,48,59,16,65,0,0,32,65,108,32,210,202,64,126,183,148,64, 99,101,0,0 }; Path path; path.loadPathFromData (pathData, sizeof (pathData)); path.scaleToFit (0, 0, height * 2.0f, height, true); return path; } ListBox listBox; OwnedArray platforms; OwnedArray toggles; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PlatformTargetsComp) }; //============================================================================== /** The Component for project creation. Features a file browser to select project destination and a list box of platform targets to generate. */ class WizardComp : public Component, private FileBrowserListener { public: WizardComp() : platformTargets(), projectName (TRANS("Project name")), modulesPathBox (getAppSettings().getStoredPath (Ids::defaultJuceModulePath, TargetOS::getThisOS()).get().toString()) { setOpaque (false); addChildAndSetID (&projectName, "projectName"); projectName.setText ("NewProject"); nameLabel.attachToComponent (&projectName, true); projectName.onTextChange = [this] { updateCreateButton(); fileBrowser.setFileName (File::createLegalFileName (projectName.getText())); }; addChildAndSetID (&projectType, "projectType"); projectType.addItemList (getWizardNames(), 1); projectType.setSelectedId (1, dontSendNotification); typeLabel.attachToComponent (&projectType, true); projectType.onChange = [this] { updateFileCreationTypes(); }; addChildAndSetID (&fileOutline, "fileOutline"); fileOutline.setColour (GroupComponent::outlineColourId, Colours::black.withAlpha (0.2f)); fileOutline.setTextLabelPosition (Justification::centred); addChildAndSetID (&targetsOutline, "targetsOutline"); targetsOutline.setColour (GroupComponent::outlineColourId, Colours::black.withAlpha (0.2f)); targetsOutline.setTextLabelPosition (Justification::centred); addChildAndSetID (&platformTargets, "platformTargets"); addChildAndSetID (&fileBrowser, "fileBrowser"); fileBrowser.setFilenameBoxLabel ("Folder:"); fileBrowser.setFileName (File::createLegalFileName (projectName.getText())); fileBrowser.addListener (this); addChildAndSetID (&createButton, "createButton"); createButton.onClick = [this] { createProject(); }; addChildAndSetID (&cancelButton, "cancelButton"); cancelButton.addShortcut (KeyPress (KeyPress::escapeKey)); cancelButton.onClick = [this] { returnToTemplatesPage(); }; addChildAndSetID (&modulesPathBox, "modulesPathBox"); addChildAndSetID (&filesToCreate, "filesToCreate"); filesToCreateLabel.attachToComponent (&filesToCreate, true); updateFileCreationTypes(); updateCreateButton(); lookAndFeelChanged(); } void paint (Graphics& g) override { g.fillAll (findColour (backgroundColourId)); } void resized() override { auto r = getLocalBounds(); auto left = r.removeFromLeft (getWidth() / 2).reduced (15); auto right = r.reduced (15); projectName.setBounds (left.removeFromTop (22).withTrimmedLeft (120)); left.removeFromTop (20); projectType.setBounds (left.removeFromTop (22).withTrimmedLeft (120)); left.removeFromTop (20); fileOutline.setBounds (left); fileBrowser.setBounds (left.reduced (25)); auto buttons = right.removeFromBottom (30); right.removeFromBottom (10); createButton.setBounds (buttons.removeFromRight (130)); buttons.removeFromRight (10); cancelButton.setBounds (buttons.removeFromRight (130)); filesToCreate.setBounds (right.removeFromTop (22).withTrimmedLeft (150)); right.removeFromTop (20); modulesPathBox.setBounds (right.removeFromTop (50)); right.removeFromTop (20); targetsOutline.setBounds (right); platformTargets.setBounds (right.reduced (25)); } void returnToTemplatesPage() { if (auto* parent = findParentComponentOfClass()) { if (parent->getNumTabs() > 0) parent->goToTab (parent->getCurrentTabIndex() - 1); } else { jassertfalse; } } void createProject() { auto* mw = Component::findParentComponentOfClass(); jassert (mw != nullptr); std::unique_ptr wizard = createWizard(); if (wizard != nullptr) { Result result (wizard->processResultsFromSetupItems (*this)); if (result.failed()) { AlertWindow::showMessageBox (AlertWindow::WarningIcon, TRANS("Create Project"), result.getErrorMessage()); return; } wizard->modulesFolder = modulesPathBox.isUsingGlobalPaths ? File (getAppSettings().getStoredPath (Ids::defaultJuceModulePath, TargetOS::getThisOS()).get().toString()) : modulesPathBox.modulesFolder; if (! isJUCEModulesFolder (wizard->modulesFolder)) { if (modulesPathBox.isUsingGlobalPaths) AlertWindow::showMessageBox (AlertWindow::AlertIconType::WarningIcon, "Invalid Global Path", "Your global JUCE module search path is invalid. Please select the folder containing your JUCE modules " "to set as the default path."); if (! wizard->selectJuceFolder()) return; if (modulesPathBox.isUsingGlobalPaths) { getAppSettings().getStoredPath (Ids::defaultJuceModulePath, TargetOS::getThisOS()).setValue (wizard->modulesFolder.getFullPathName(), nullptr); ProjucerApplication::getApp().rescanJUCEPathModules(); } } auto projectDir = fileBrowser.getSelectedFile (0); std::unique_ptr project (wizard->runWizard (*this, projectName.getText(), projectDir, modulesPathBox.isUsingGlobalPaths)); if (project != nullptr) { mw->openFile (project->getFile()); getAppSettings().lastWizardFolder = projectDir.getParentDirectory(); } } } void updateFileCreationTypes() { StringArray items; std::unique_ptr wizard = createWizard(); if (wizard != nullptr) items = wizard->getFileCreationOptions(); filesToCreate.clear(); filesToCreate.addItemList (items, 1); filesToCreate.setSelectedId (1, dontSendNotification); } void selectionChanged() override {} void fileClicked (const File&, const MouseEvent&) override {} void fileDoubleClicked (const File&) override {} void browserRootChanged (const File&) override { fileBrowser.setFileName (File::createLegalFileName (projectName.getText())); } int getFileCreationComboID() const { return filesToCreate.getSelectedItemIndex(); } ComboBox projectType, filesToCreate; PlatformTargetsComp platformTargets; private: TextEditor projectName; Label nameLabel { {}, TRANS("Project Name") + ":" }; Label typeLabel { {}, TRANS("Project Type") + ":" }; Label filesToCreateLabel { {}, TRANS("Files to Auto-Generate") + ":" }; FileBrowserComponent fileBrowser { FileBrowserComponent::saveMode | FileBrowserComponent::canSelectDirectories | FileBrowserComponent::doNotClearFileNameOnRootChange, NewProjectWizardClasses::getLastWizardFolder(), nullptr, nullptr }; GroupComponent fileOutline { {}, TRANS("Project Folder") + ":" }; GroupComponent targetsOutline { {}, TRANS("Target Platforms") + ":" }; TextButton createButton { TRANS("Create") + "..." }; TextButton cancelButton { TRANS("Cancel") }; ModulesFolderPathBox modulesPathBox; std::unique_ptr createWizard() { return createWizardType (projectType.getSelectedItemIndex()); } void updateCreateButton() { createButton.setEnabled (projectName.getText().trim().isNotEmpty()); } void lookAndFeelChanged() override { projectName.setColour (TextEditor::backgroundColourId, findColour (backgroundColourId)); projectName.setColour (TextEditor::textColourId, findColour (defaultTextColourId)); projectName.setColour (TextEditor::outlineColourId, findColour (defaultTextColourId)); projectName.applyFontToAllText (projectName.getFont()); fileBrowser.resized(); } };