/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2022 - Raw Material Software Limited JUCE is an open source library subject to commercial or open-source licensing. By using JUCE, you agree to the terms of both the JUCE 7 End-User License Agreement and JUCE Privacy Policy. End User License Agreement: www.juce.com/juce-7-licence Privacy Policy: www.juce.com/juce-privacy-policy Or: You may also use this code under the terms of the GPL v3 (see www.gnu.org/licenses). 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 //============================================================================== static String getWidthLimitedStringFromVarArray (const var& varArray) noexcept { if (! varArray.isArray()) return {}; int numLines = 1; const int lineWidth = 100; const String indent (" "); String str; if (auto* arr = varArray.getArray()) { for (auto& v : *arr) { if ((str.length() + v.toString().length()) > (lineWidth * numLines)) { str += newLine; str += indent; ++numLines; } str += v.toString() + (arr->indexOf (v) != arr->size() - 1 ? ", " : ""); } } return str; } //============================================================================== class PIPCreatorWindowComponent : public Component, private ValueTree::Listener { public: PIPCreatorWindowComponent() { lf.reset (new PIPCreatorLookAndFeel()); setLookAndFeel (lf.get()); addAndMakeVisible (propertyViewport); propertyViewport.setViewedComponent (&propertyGroup, false); buildProps(); addAndMakeVisible (createButton); createButton.onClick = [this] { chooser = std::make_unique ("Save PIP File", File::getSpecialLocation (File::SpecialLocationType::userDesktopDirectory) .getChildFile (nameValue.get().toString() + ".h")); auto browserFlags = FileBrowserComponent::saveMode | FileBrowserComponent::canSelectFiles | FileBrowserComponent::warnAboutOverwriting; chooser->launchAsync (browserFlags, [this] (const FileChooser& fc) { const auto result = fc.getResult(); if (result != File{}) createPIPFile (result); }); }; pipTree.addListener (this); } ~PIPCreatorWindowComponent() override { setLookAndFeel (nullptr); } void resized() override { auto bounds = getLocalBounds(); createButton.setBounds (bounds.removeFromBottom (50).reduced (100, 10)); propertyGroup.updateSize (0, 0, getWidth() - propertyViewport.getScrollBarThickness()); propertyViewport.setBounds (bounds); } private: //============================================================================== struct PIPCreatorLookAndFeel : public ProjucerLookAndFeel { PIPCreatorLookAndFeel() {} Rectangle getPropertyComponentContentPosition (PropertyComponent& component) { auto textW = jmin (200, component.getWidth() / 3); return { textW, 0, component.getWidth() - textW, component.getHeight() - 1 }; } }; void lookAndFeelChanged() override { lf->setColourScheme (ProjucerApplication::getApp().lookAndFeel.getCurrentColourScheme()); lf->setupColours(); } //============================================================================== void buildProps() { PropertyListBuilder builder; builder.add (new TextPropertyComponent (nameValue, "Name", 256, false), "The name of your JUCE project."); builder.add (new TextPropertyComponent (versionValue, "Version", 16, false), "This will be used for the \"Project Version\" field in the Projucer."); builder.add (new TextPropertyComponent (vendorValue, "Vendor", 2048, false), "This will be used for the \"Company Name\" field in the Projucer."); builder.add (new TextPropertyComponent (websiteValue, "Website", 2048, false), "This will be used for the \"Company Website\" field in the Projucer"); builder.add (new TextPropertyComponent (descriptionValue, "Description", 2048, true), "A short description of your JUCE project."); { Array moduleVars; for (auto& m : getJUCEModules()) moduleVars.add (m); builder.add (new MultiChoicePropertyComponent (dependenciesValue, "Dependencies", getJUCEModules(), moduleVars), "The JUCE modules that should be added to your project."); } { Array exporterVars; StringArray exporterNames; for (auto& exporterTypeInfo : ProjectExporter::getExporterTypeInfos()) { exporterVars.add (exporterTypeInfo.identifier.toString()); exporterNames.add (exporterTypeInfo.displayName); } builder.add (new MultiChoicePropertyComponent (exportersValue, "Exporters", exporterNames, exporterVars), "The exporters that should be added to your project."); } builder.add (new TextPropertyComponent (moduleFlagsValue, "Module Flags", 2048, true), "Use this to set one, or many, of the JUCE module flags"); builder.add (new TextPropertyComponent (definesValue, "Defines", 2048, true), "This sets some global preprocessor definitions for your project. Used to populate the \"Preprocessor Definitions\" field in the Projucer."); builder.add (new ChoicePropertyComponent (typeValue, "Type", { "Component", "Plugin", "Console Application" }, { "Component", "AudioProcessor", "Console" }), "The project type."); builder.add (new TextPropertyComponent (mainClassValue, "Main Class", 2048, false), "The name of the main class that should be instantiated. " "There can only be one main class and it must have a default constructor. " "Depending on the type, this may need to inherit from a specific JUCE class"); builder.add (new ChoicePropertyComponent (useLocalCopyValue, "Use Local Copy"), "Enable this to specify that the PIP file should be copied to the generated project directory instead of just referred to."); propertyGroup.setProperties (builder); } //============================================================================== void valueTreePropertyChanged (ValueTree&, const Identifier& identifier) override { if (identifier == Ids::type) { auto type = typeValue.get().toString(); if (type == "Component") { nameValue.setDefault ("MyComponentPIP"); dependenciesValue.setDefault (getModulesRequiredForComponent()); mainClassValue.setDefault ("MyComponent"); } else if (type == "AudioProcessor") { nameValue.setDefault ("MyPluginPIP"); dependenciesValue.setDefault (getModulesRequiredForAudioProcessor()); mainClassValue.setDefault ("MyPlugin"); } else if (type == "Console") { nameValue.setDefault ("MyConsolePIP"); dependenciesValue.setDefault (getModulesRequiredForConsole()); mainClassValue.setDefault ({}); } MessageManager::callAsync ([this] { buildProps(); resized(); }); } } //============================================================================== String getFormattedMetadataString() const noexcept { StringArray metadata; { StringArray section; if (nameValue.get().toString().isNotEmpty()) section.add (" name: " + nameValue.get().toString()); if (versionValue.get().toString().isNotEmpty()) section.add (" version: " + versionValue.get().toString()); if (vendorValue.get().toString().isNotEmpty()) section.add (" vendor: " + vendorValue.get().toString()); if (websiteValue.get().toString().isNotEmpty()) section.add (" website: " + websiteValue.get().toString()); if (descriptionValue.get().toString().isNotEmpty()) section.add (" description: " + descriptionValue.get().toString()); if (! section.isEmpty()) metadata.add (section.joinIntoString (getPreferredLineFeed())); } { StringArray section; auto dependenciesString = getWidthLimitedStringFromVarArray (dependenciesValue.get()); if (dependenciesString.isNotEmpty()) section.add (" dependencies: " + dependenciesString); auto exportersString = getWidthLimitedStringFromVarArray (exportersValue.get()); if (exportersString.isNotEmpty()) section.add (" exporters: " + exportersString); if (! section.isEmpty()) metadata.add (section.joinIntoString (getPreferredLineFeed())); } { StringArray section; if (moduleFlagsValue.get().toString().isNotEmpty()) section.add (" moduleFlags: " + moduleFlagsValue.get().toString()); if (definesValue.get().toString().isNotEmpty()) section.add (" defines: " + definesValue.get().toString()); if (! section.isEmpty()) metadata.add (section.joinIntoString (getPreferredLineFeed())); } { StringArray section; if (typeValue.get().toString().isNotEmpty()) section.add (" type: " + typeValue.get().toString()); if (mainClassValue.get().toString().isNotEmpty()) section.add (" mainClass: " + mainClassValue.get().toString()); if (! section.isEmpty()) metadata.add (section.joinIntoString (getPreferredLineFeed())); } { StringArray section; if (useLocalCopyValue.get()) section.add (" useLocalCopy: " + useLocalCopyValue.get().toString()); if (! section.isEmpty()) metadata.add (section.joinIntoString (getPreferredLineFeed())); } return metadata.joinIntoString (String (getPreferredLineFeed()) + getPreferredLineFeed()); } void createPIPFile (File fileToSave) { String fileTemplate (BinaryData::jucer_PIPTemplate_h); fileTemplate = fileTemplate.replace ("%%pip_metadata%%", getFormattedMetadataString()); auto type = typeValue.get().toString(); if (type == "Component") { String componentCode (BinaryData::jucer_ContentCompSimpleTemplate_h); componentCode = componentCode.substring (componentCode.indexOf ("class %%content_component_class%%")) .replace ("%%content_component_class%%", mainClassValue.get().toString()); fileTemplate = fileTemplate.replace ("%%pip_code%%", componentCode); } else if (type == "AudioProcessor") { String audioProcessorCode (BinaryData::jucer_PIPAudioProcessorTemplate_h); audioProcessorCode = audioProcessorCode.replace ("%%class_name%%", mainClassValue.get().toString()) .replace ("%%name%%", nameValue.get().toString()); fileTemplate = fileTemplate.replace ("%%pip_code%%", audioProcessorCode); } else if (type == "Console") { String consoleCode (BinaryData::jucer_MainConsoleAppTemplate_cpp); consoleCode = consoleCode.substring (consoleCode.indexOf ("int main (int argc, char* argv[])")); fileTemplate = fileTemplate.replace ("%%pip_code%%", consoleCode); } if (fileToSave.create()) fileToSave.replaceWithText (fileTemplate); } //============================================================================== ValueTree pipTree { "PIPSettings" }; ValueTreePropertyWithDefault nameValue { pipTree, Ids::name, nullptr, "MyComponentPIP" }, versionValue { pipTree, Ids::version, nullptr }, vendorValue { pipTree, Ids::vendor, nullptr }, websiteValue { pipTree, Ids::website, nullptr }, descriptionValue { pipTree, Ids::description, nullptr }, dependenciesValue { pipTree, Ids::dependencies_, nullptr, getModulesRequiredForComponent(), "," }, exportersValue { pipTree, Ids::exporters, nullptr, StringArray (ProjectExporter::getCurrentPlatformExporterTypeInfo().identifier.toString()), "," }, moduleFlagsValue { pipTree, Ids::moduleFlags, nullptr, "JUCE_STRICT_REFCOUNTEDPOINTER=1" }, definesValue { pipTree, Ids::defines, nullptr }, typeValue { pipTree, Ids::type, nullptr, "Component" }, mainClassValue { pipTree, Ids::mainClass, nullptr, "MyComponent" }, useLocalCopyValue { pipTree, Ids::useLocalCopy, nullptr, false }; std::unique_ptr lf; Viewport propertyViewport; PropertyGroupComponent propertyGroup { "PIP Creator", {} }; TextButton createButton { "Create PIP" }; std::unique_ptr chooser; //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PIPCreatorWindowComponent) };