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.

335 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. lf.reset (new PIPCreatorLookAndFeel());
  52. setLookAndFeel (lf.get());
  53. addAndMakeVisible (propertyViewport);
  54. propertyViewport.setViewedComponent (&propertyGroup, false);
  55. buildProps();
  56. addAndMakeVisible (createButton);
  57. createButton.onClick = [this]
  58. {
  59. FileChooser fc ("Save PIP File",
  60. File::getSpecialLocation (File::SpecialLocationType::userDesktopDirectory)
  61. .getChildFile (nameValue.get().toString() + ".h"));
  62. fc.browseForFileToSave (true);
  63. createPIPFile (fc.getResult());
  64. };
  65. pipTree.addListener (this);
  66. }
  67. ~PIPCreatorWindowComponent()
  68. {
  69. setLookAndFeel (nullptr);
  70. }
  71. void resized() override
  72. {
  73. auto bounds = getLocalBounds();
  74. createButton.setBounds (bounds.removeFromBottom (50).reduced (100, 10));
  75. propertyGroup.updateSize (0, 0, getWidth() - propertyViewport.getScrollBarThickness());
  76. propertyViewport.setBounds (bounds);
  77. }
  78. private:
  79. //==============================================================================
  80. struct PIPCreatorLookAndFeel : public ProjucerLookAndFeel
  81. {
  82. PIPCreatorLookAndFeel() {}
  83. Rectangle<int> getPropertyComponentContentPosition (PropertyComponent& component)
  84. {
  85. auto textW = jmin (200, component.getWidth() / 3);
  86. return { textW, 0, component.getWidth() - textW, component.getHeight() - 1 };
  87. }
  88. };
  89. //==============================================================================
  90. void buildProps()
  91. {
  92. PropertyListBuilder builder;
  93. builder.add (new TextPropertyComponent (nameValue, "Name", 256, false),
  94. "The name of your JUCE project.");
  95. builder.add (new TextPropertyComponent (versionValue, "Version", 16, false),
  96. "This will be used for the \"Project Version\" field in the Projucer.");
  97. builder.add (new TextPropertyComponent (vendorValue, "Vendor", 2048, false),
  98. "This will be used for the \"Company Name\" field in the Projucer.");
  99. builder.add (new TextPropertyComponent (websiteValue, "Website", 2048, false),
  100. "This will be used for the \"Company Website\" field in the Projucer");
  101. builder.add (new TextPropertyComponent (descriptionValue, "Description", 2048, true),
  102. "A short description of your JUCE project.");
  103. {
  104. Array<var> moduleVars;
  105. for (auto& m : getJUCEModules())
  106. moduleVars.add (m);
  107. builder.add (new MultiChoicePropertyComponent (dependenciesValue, "Dependencies",
  108. getJUCEModules(), moduleVars),
  109. "The JUCE modules that should be added to your project.");
  110. }
  111. {
  112. Array<var> exporterVars;
  113. for (auto& e : ProjectExporter::getExporterValueTreeNames())
  114. exporterVars.add (e.toLowerCase());
  115. builder.add (new MultiChoicePropertyComponent (exportersValue, "Exporters",
  116. ProjectExporter::getExporterNames(), exporterVars),
  117. "The exporters that should be added to your project.");
  118. }
  119. builder.add (new TextPropertyComponent (moduleFlagsValue, "Module Flags", 2048, true),
  120. "Use this to set one, or many, of the JUCE module flags");
  121. builder.add (new TextPropertyComponent (definesValue, "Defines", 2048, true),
  122. "This sets some global preprocessor definitions for your project. Used to populate the \"Preprocessor Definitions\" field in the Projucer.");
  123. builder.add (new ChoicePropertyComponent (typeValue, "Type",
  124. { "Component", "Plugin", "Console Application" },
  125. { "Component", "AudioProcessor", "Console" }),
  126. "The project type.");
  127. builder.add (new TextPropertyComponent (mainClassValue, "Main Class", 2048, false),
  128. "The name of the main class that should be instantiated. "
  129. "There can only be one main class and it must have a default constructor. "
  130. "Depending on the type, this may need to inherit from a specific JUCE class");
  131. builder.add (new ChoicePropertyComponent (useLocalCopyValue, "Use Local Copy"),
  132. "Enable this to specify that the PIP file should be copied to the generated project directory instead of just referred to.");
  133. propertyGroup.setProperties (builder);
  134. }
  135. //==============================================================================
  136. void valueTreePropertyChanged (ValueTree&, const Identifier& id) override
  137. {
  138. if (id == Ids::type)
  139. {
  140. auto type = typeValue.get().toString();
  141. if (type == "Component")
  142. {
  143. nameValue.setDefault ("MyComponentPIP");
  144. dependenciesValue.setDefault (getModulesRequiredForComponent());
  145. mainClassValue.setDefault ("MyComponent");
  146. }
  147. else if (type == "AudioProcessor")
  148. {
  149. nameValue.setDefault ("MyPluginPIP");
  150. dependenciesValue.setDefault (getModulesRequiredForAudioProcessor());
  151. mainClassValue.setDefault ("MyPlugin");
  152. }
  153. else if (type == "Console")
  154. {
  155. nameValue.setDefault ("MyConsolePIP");
  156. dependenciesValue.setDefault (getModulesRequiredForConsole());
  157. mainClassValue.setDefault ({});
  158. }
  159. MessageManager::callAsync ([this]
  160. {
  161. buildProps();
  162. resized();
  163. });
  164. }
  165. }
  166. void valueTreeChildAdded (ValueTree&, ValueTree&) override {}
  167. void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override {}
  168. void valueTreeChildOrderChanged (ValueTree&, int, int) override {}
  169. void valueTreeParentChanged (ValueTree&) override {}
  170. //==============================================================================
  171. String getFormattedMetadataString() const noexcept
  172. {
  173. StringArray metadata;
  174. {
  175. StringArray section;
  176. if (nameValue.get().toString().isNotEmpty()) section.add (" name: " + nameValue.get().toString());
  177. if (versionValue.get().toString().isNotEmpty()) section.add (" version: " + versionValue.get().toString());
  178. if (vendorValue.get().toString().isNotEmpty()) section.add (" vendor: " + vendorValue.get().toString());
  179. if (websiteValue.get().toString().isNotEmpty()) section.add (" website: " + websiteValue.get().toString());
  180. if (descriptionValue.get().toString().isNotEmpty()) section.add (" description: " + descriptionValue.get().toString());
  181. if (! section.isEmpty())
  182. metadata.add (section.joinIntoString (getPreferredLinefeed()));
  183. }
  184. {
  185. StringArray section;
  186. auto dependenciesString = getWidthLimitedStringFromVarArray (dependenciesValue.get());
  187. if (dependenciesString.isNotEmpty()) section.add (" dependencies: " + dependenciesString);
  188. auto exportersString = getWidthLimitedStringFromVarArray (exportersValue.get());
  189. if (exportersString.isNotEmpty()) section.add (" exporters: " + exportersString);
  190. if (! section.isEmpty())
  191. metadata.add (section.joinIntoString (getPreferredLinefeed()));
  192. }
  193. {
  194. StringArray section;
  195. if (moduleFlagsValue.get().toString().isNotEmpty()) section.add (" moduleFlags: " + moduleFlagsValue.get().toString());
  196. if (definesValue.get().toString().isNotEmpty()) section.add (" defines: " + definesValue.get().toString());
  197. if (! section.isEmpty())
  198. metadata.add (section.joinIntoString (getPreferredLinefeed()));
  199. }
  200. {
  201. StringArray section;
  202. if (typeValue.get().toString().isNotEmpty()) section.add (" type: " + typeValue.get().toString());
  203. if (mainClassValue.get().toString().isNotEmpty()) section.add (" mainClass: " + mainClassValue.get().toString());
  204. if (! section.isEmpty())
  205. metadata.add (section.joinIntoString (getPreferredLinefeed()));
  206. }
  207. {
  208. StringArray section;
  209. if (useLocalCopyValue.get()) section.add (" useLocalCopy: " + useLocalCopyValue.get().toString());
  210. if (! section.isEmpty())
  211. metadata.add (section.joinIntoString (getPreferredLinefeed()));
  212. }
  213. return metadata.joinIntoString (String (getPreferredLinefeed()) + getPreferredLinefeed());
  214. }
  215. void createPIPFile (File fileToSave)
  216. {
  217. String fileTemplate (BinaryData::jucer_PIPTemplate_h);
  218. fileTemplate = fileTemplate.replace ("%%pip_metadata%%", getFormattedMetadataString());
  219. auto type = typeValue.get().toString();
  220. if (type == "Component")
  221. {
  222. String componentCode (BinaryData::jucer_ContentCompSimpleTemplate_h);
  223. componentCode = componentCode.substring (componentCode.indexOf ("class %%content_component_class%%"))
  224. .replace ("%%content_component_class%%", mainClassValue.get().toString());
  225. fileTemplate = fileTemplate.replace ("%%pip_code%%", componentCode);
  226. }
  227. else if (type == "AudioProcessor")
  228. {
  229. String audioProcessorCode (BinaryData::jucer_PIPAudioProcessorTemplate_h);
  230. audioProcessorCode = audioProcessorCode.replace ("%%class_name%%", mainClassValue.get().toString())
  231. .replace ("%%name%%", nameValue.get().toString());
  232. fileTemplate = fileTemplate.replace ("%%pip_code%%", audioProcessorCode);
  233. }
  234. else if (type == "Console")
  235. {
  236. String consoleCode (BinaryData::jucer_MainConsoleAppTemplate_cpp);
  237. consoleCode = consoleCode.substring (consoleCode.indexOf ("int main (int argc, char* argv[])"));
  238. fileTemplate = fileTemplate.replace ("%%pip_code%%", consoleCode);
  239. }
  240. if (fileToSave.create())
  241. fileToSave.replaceWithText (fileTemplate);
  242. }
  243. //==============================================================================
  244. std::unique_ptr<LookAndFeel> lf;
  245. Viewport propertyViewport;
  246. PropertyGroupComponent propertyGroup { "PIP Creator", { getIcons().juceLogo, Colours::transparentBlack } };
  247. TextButton createButton { "Create PIP" };
  248. ValueTree pipTree { "PIPSettings" };
  249. ValueWithDefault nameValue { pipTree, Ids::name, nullptr, "MyComponentPIP" },
  250. versionValue { pipTree, Ids::version, nullptr },
  251. vendorValue { pipTree, Ids::vendor, nullptr },
  252. websiteValue { pipTree, Ids::website, nullptr },
  253. descriptionValue { pipTree, Ids::description, nullptr },
  254. dependenciesValue { pipTree, Ids::dependencies_, nullptr, getModulesRequiredForComponent(), "," },
  255. exportersValue { pipTree, Ids::exporters, nullptr,
  256. StringArray (ProjectExporter::getValueTreeNameForExporter (ProjectExporter::getCurrentPlatformExporterName()).toLowerCase()), "," },
  257. moduleFlagsValue { pipTree, Ids::moduleFlags, nullptr },
  258. definesValue { pipTree, Ids::defines, nullptr },
  259. typeValue { pipTree, Ids::type, nullptr, "Component" },
  260. mainClassValue { pipTree, Ids::mainClass, nullptr, "MyComponent" },
  261. useLocalCopyValue { pipTree, Ids::useLocalCopy, nullptr, false };
  262. //==============================================================================
  263. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PIPCreatorWindowComponent)
  264. };