The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

334 lines
14KB

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