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