1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    By using JUCE, you agree to the terms of both the JUCE 6 End-User License
11    Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
12 
13    End User License Agreement: www.juce.com/juce-6-licence
14    Privacy Policy: www.juce.com/juce-privacy-policy
15 
16    Or: You may also use this code under the terms of the GPL v3 (see
17    www.gnu.org/licenses).
18 
19    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21    DISCLAIMED.
22 
23   ==============================================================================
24 */
25 
26 #pragma once
27 
28 
29 //==============================================================================
getWidthLimitedStringFromVarArray(const var & varArray)30 static String getWidthLimitedStringFromVarArray (const var& varArray) noexcept
31 {
32     if (! varArray.isArray())
33         return {};
34 
35     int numLines = 1;
36     const int lineWidth = 100;
37     const String indent ("                    ");
38 
39     String str;
40     if (auto* arr = varArray.getArray())
41     {
42         for (auto& v : *arr)
43         {
44             if ((str.length() + v.toString().length()) > (lineWidth * numLines))
45             {
46                 str += newLine;
47                 str += indent;
48 
49                 ++numLines;
50             }
51 
52             str += v.toString() + (arr->indexOf (v) != arr->size() - 1 ? ", " : "");
53         }
54     }
55 
56     return str;
57 }
58 
59 //==============================================================================
60 class PIPCreatorWindowComponent    : public Component,
61                                      private ValueTree::Listener
62 {
63 public:
PIPCreatorWindowComponent()64     PIPCreatorWindowComponent()
65     {
66         lf.reset (new PIPCreatorLookAndFeel());
67         setLookAndFeel (lf.get());
68 
69         addAndMakeVisible (propertyViewport);
70         propertyViewport.setViewedComponent (&propertyGroup, false);
71         buildProps();
72 
73         addAndMakeVisible (createButton);
74         createButton.onClick = [this]
75         {
76             FileChooser fc ("Save PIP File",
77                             File::getSpecialLocation (File::SpecialLocationType::userDesktopDirectory)
78                                  .getChildFile (nameValue.get().toString() + ".h"));
79 
80             fc.browseForFileToSave (true);
81 
82             createPIPFile (fc.getResult());
83         };
84 
85         pipTree.addListener (this);
86     }
87 
~PIPCreatorWindowComponent()88     ~PIPCreatorWindowComponent() override
89     {
90         setLookAndFeel (nullptr);
91     }
92 
resized()93     void resized() override
94     {
95         auto bounds = getLocalBounds();
96 
97         createButton.setBounds (bounds.removeFromBottom (50).reduced (100, 10));
98 
99         propertyGroup.updateSize (0, 0, getWidth() - propertyViewport.getScrollBarThickness());
100         propertyViewport.setBounds (bounds);
101     }
102 
103 private:
104     //==============================================================================
105     struct PIPCreatorLookAndFeel    : public ProjucerLookAndFeel
106     {
PIPCreatorLookAndFeelPIPCreatorLookAndFeel107         PIPCreatorLookAndFeel()    {}
108 
getPropertyComponentContentPositionPIPCreatorLookAndFeel109         Rectangle<int> getPropertyComponentContentPosition (PropertyComponent& component)
110         {
111             auto textW = jmin (200, component.getWidth() / 3);
112             return { textW, 0, component.getWidth() - textW, component.getHeight() - 1 };
113         }
114     };
115 
lookAndFeelChanged()116     void lookAndFeelChanged() override
117     {
118         lf->setColourScheme (ProjucerApplication::getApp().lookAndFeel.getCurrentColourScheme());
119         lf->setupColours();
120     }
121 
122     //==============================================================================
buildProps()123     void buildProps()
124     {
125         PropertyListBuilder builder;
126 
127         builder.add (new TextPropertyComponent (nameValue, "Name", 256, false),
128                      "The name of your JUCE project.");
129         builder.add (new TextPropertyComponent (versionValue, "Version", 16, false),
130                      "This will be used for the \"Project Version\" field in the Projucer.");
131         builder.add (new TextPropertyComponent (vendorValue, "Vendor", 2048, false),
132                      "This will be used for the \"Company Name\" field in the Projucer.");
133         builder.add (new TextPropertyComponent (websiteValue, "Website", 2048, false),
134                      "This will be used for the \"Company Website\" field in the Projucer");
135         builder.add (new TextPropertyComponent (descriptionValue, "Description", 2048, true),
136                      "A short description of your JUCE project.");
137 
138         {
139             Array<var> moduleVars;
140             for (auto& m : getJUCEModules())
141                 moduleVars.add (m);
142 
143             builder.add (new MultiChoicePropertyComponent (dependenciesValue, "Dependencies",
144                                                            getJUCEModules(), moduleVars),
145                          "The JUCE modules that should be added to your project.");
146         }
147 
148         {
149             Array<var> exporterVars;
150             StringArray exporterNames;
151 
152             for (auto& exporterTypeInfo : ProjectExporter::getExporterTypeInfos())
153             {
154                 exporterVars.add (exporterTypeInfo.identifier.toString());
155                 exporterNames.add (exporterTypeInfo.displayName);
156             }
157 
158             builder.add (new MultiChoicePropertyComponent (exportersValue, "Exporters", exporterNames, exporterVars),
159                          "The exporters that should be added to your project.");
160         }
161 
162         builder.add (new TextPropertyComponent (moduleFlagsValue, "Module Flags", 2048, true),
163                      "Use this to set one, or many, of the JUCE module flags");
164         builder.add (new TextPropertyComponent (definesValue, "Defines", 2048, true),
165                      "This sets some global preprocessor definitions for your project. Used to populate the \"Preprocessor Definitions\" field in the Projucer.");
166         builder.add (new ChoicePropertyComponent (typeValue, "Type",
167                                                   { "Component", "Plugin",         "Console Application" },
168                                                   { "Component", "AudioProcessor", "Console" }),
169                      "The project type.");
170 
171         builder.add (new TextPropertyComponent (mainClassValue, "Main Class", 2048, false),
172                      "The name of the main class that should be instantiated. "
173                      "There can only be one main class and it must have a default constructor. "
174                      "Depending on the type, this may need to inherit from a specific JUCE class");
175 
176         builder.add (new ChoicePropertyComponent (useLocalCopyValue, "Use Local Copy"),
177                      "Enable this to specify that the PIP file should be copied to the generated project directory instead of just referred to.");
178 
179         propertyGroup.setProperties (builder);
180     }
181 
182     //==============================================================================
valueTreePropertyChanged(ValueTree &,const Identifier & identifier)183     void valueTreePropertyChanged (ValueTree&, const Identifier& identifier) override
184     {
185         if (identifier == Ids::type)
186         {
187             auto type = typeValue.get().toString();
188 
189             if (type == "Component")
190             {
191                 nameValue.setDefault ("MyComponentPIP");
192                 dependenciesValue.setDefault (getModulesRequiredForComponent());
193                 mainClassValue.setDefault ("MyComponent");
194             }
195             else if (type == "AudioProcessor")
196             {
197                 nameValue.setDefault ("MyPluginPIP");
198                 dependenciesValue.setDefault (getModulesRequiredForAudioProcessor());
199                 mainClassValue.setDefault ("MyPlugin");
200             }
201             else if (type == "Console")
202             {
203                 nameValue.setDefault ("MyConsolePIP");
204                 dependenciesValue.setDefault (getModulesRequiredForConsole());
205                 mainClassValue.setDefault ({});
206             }
207 
208             MessageManager::callAsync ([this]
209             {
210                 buildProps();
211                 resized();
212             });
213         }
214     }
215 
216     //==============================================================================
getFormattedMetadataString()217     String getFormattedMetadataString() const noexcept
218     {
219         StringArray metadata;
220 
221         {
222             StringArray section;
223 
224             if (nameValue.get().toString().isNotEmpty())          section.add ("  name:             " + nameValue.get().toString());
225             if (versionValue.get().toString().isNotEmpty())       section.add ("  version:          " + versionValue.get().toString());
226             if (vendorValue.get().toString().isNotEmpty())        section.add ("  vendor:           " + vendorValue.get().toString());
227             if (websiteValue.get().toString().isNotEmpty())       section.add ("  website:          " + websiteValue.get().toString());
228             if (descriptionValue.get().toString().isNotEmpty())   section.add ("  description:      " + descriptionValue.get().toString());
229 
230             if (! section.isEmpty())
231                 metadata.add (section.joinIntoString (getPreferredLineFeed()));
232         }
233 
234         {
235             StringArray section;
236 
237             auto dependenciesString = getWidthLimitedStringFromVarArray (dependenciesValue.get());
238             if (dependenciesString.isNotEmpty())                  section.add ("  dependencies:     " + dependenciesString);
239 
240             auto exportersString = getWidthLimitedStringFromVarArray (exportersValue.get());
241             if (exportersString.isNotEmpty())                     section.add ("  exporters:        " + exportersString);
242 
243             if (! section.isEmpty())
244                 metadata.add (section.joinIntoString (getPreferredLineFeed()));
245         }
246 
247         {
248             StringArray section;
249 
250             if (moduleFlagsValue.get().toString().isNotEmpty())   section.add ("  moduleFlags:      " + moduleFlagsValue.get().toString());
251             if (definesValue.get().toString().isNotEmpty())       section.add ("  defines:          " + definesValue.get().toString());
252 
253             if (! section.isEmpty())
254                 metadata.add (section.joinIntoString (getPreferredLineFeed()));
255         }
256 
257         {
258             StringArray section;
259 
260             if (typeValue.get().toString().isNotEmpty())          section.add ("  type:             " + typeValue.get().toString());
261             if (mainClassValue.get().toString().isNotEmpty())     section.add ("  mainClass:        " + mainClassValue.get().toString());
262 
263             if (! section.isEmpty())
264                 metadata.add (section.joinIntoString (getPreferredLineFeed()));
265         }
266 
267         {
268             StringArray section;
269 
270             if (useLocalCopyValue.get())                          section.add ("  useLocalCopy:     " + useLocalCopyValue.get().toString());
271 
272             if (! section.isEmpty())
273                 metadata.add (section.joinIntoString (getPreferredLineFeed()));
274         }
275 
276         return metadata.joinIntoString (String (getPreferredLineFeed()) + getPreferredLineFeed());
277     }
278 
createPIPFile(File fileToSave)279     void createPIPFile (File fileToSave)
280     {
281         String fileTemplate (BinaryData::jucer_PIPTemplate_h);
282         fileTemplate = fileTemplate.replace ("%%pip_metadata%%", getFormattedMetadataString());
283 
284         auto type = typeValue.get().toString();
285 
286         if (type == "Component")
287         {
288             String componentCode (BinaryData::jucer_ContentCompSimpleTemplate_h);
289             componentCode = componentCode.substring (componentCode.indexOf ("class %%content_component_class%%"))
290                                          .replace ("%%content_component_class%%", mainClassValue.get().toString());
291 
292             fileTemplate = fileTemplate.replace ("%%pip_code%%", componentCode);
293         }
294         else if (type == "AudioProcessor")
295         {
296             String audioProcessorCode (BinaryData::jucer_PIPAudioProcessorTemplate_h);
297             audioProcessorCode = audioProcessorCode.replace ("%%class_name%%", mainClassValue.get().toString())
298                                                    .replace ("%%name%%", nameValue.get().toString());
299 
300             fileTemplate = fileTemplate.replace ("%%pip_code%%", audioProcessorCode);
301         }
302         else if (type == "Console")
303         {
304             String consoleCode (BinaryData::jucer_MainConsoleAppTemplate_cpp);
305             consoleCode = consoleCode.substring (consoleCode.indexOf ("int main (int argc, char* argv[])"));
306 
307             fileTemplate = fileTemplate.replace ("%%pip_code%%", consoleCode);
308         }
309 
310         if (fileToSave.create())
311             fileToSave.replaceWithText (fileTemplate);
312     }
313 
314     //==============================================================================
315     ValueTree pipTree  { "PIPSettings" };
316     ValueWithDefault nameValue          { pipTree, Ids::name,          nullptr, "MyComponentPIP" },
317                      versionValue       { pipTree, Ids::version,       nullptr },
318                      vendorValue        { pipTree, Ids::vendor,        nullptr },
319                      websiteValue       { pipTree, Ids::website,       nullptr },
320                      descriptionValue   { pipTree, Ids::description,   nullptr },
321                      dependenciesValue  { pipTree, Ids::dependencies_, nullptr, getModulesRequiredForComponent(), "," },
322                      exportersValue     { pipTree, Ids::exporters,     nullptr, StringArray (ProjectExporter::getCurrentPlatformExporterTypeInfo().identifier.toString()), "," },
323                      moduleFlagsValue   { pipTree, Ids::moduleFlags,   nullptr, "JUCE_STRICT_REFCOUNTEDPOINTER=1" },
324                      definesValue       { pipTree, Ids::defines,       nullptr },
325                      typeValue          { pipTree, Ids::type,          nullptr, "Component" },
326                      mainClassValue     { pipTree, Ids::mainClass,     nullptr, "MyComponent" },
327                      useLocalCopyValue  { pipTree, Ids::useLocalCopy,  nullptr, false };
328 
329     std::unique_ptr<PIPCreatorLookAndFeel> lf;
330 
331     Viewport propertyViewport;
332     PropertyGroupComponent propertyGroup  { "PIP Creator", { getIcons().juceLogo, Colours::transparentBlack } };
333 
334     TextButton createButton  { "Create PIP" };
335 
336     //==============================================================================
337     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PIPCreatorWindowComponent)
338 };
339