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.

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