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.

339 lines
14KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2020 - Raw Material Software Limited
  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 6 End-User License
  8. Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
  9. End User License Agreement: www.juce.com/juce-6-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. #pragma once
  19. //==============================================================================
  20. static String getWidthLimitedStringFromVarArray (const var& varArray) noexcept
  21. {
  22. if (! varArray.isArray())
  23. return {};
  24. int numLines = 1;
  25. const int lineWidth = 100;
  26. const String indent (" ");
  27. String str;
  28. if (auto* arr = varArray.getArray())
  29. {
  30. for (auto& v : *arr)
  31. {
  32. if ((str.length() + v.toString().length()) > (lineWidth * numLines))
  33. {
  34. str += newLine;
  35. str += indent;
  36. ++numLines;
  37. }
  38. str += v.toString() + (arr->indexOf (v) != arr->size() - 1 ? ", " : "");
  39. }
  40. }
  41. return str;
  42. }
  43. //==============================================================================
  44. class PIPCreatorWindowComponent : public Component,
  45. private ValueTree::Listener
  46. {
  47. public:
  48. PIPCreatorWindowComponent()
  49. {
  50. lf.reset (new PIPCreatorLookAndFeel());
  51. setLookAndFeel (lf.get());
  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() override
  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. void lookAndFeelChanged() override
  89. {
  90. lf->setColourScheme (ProjucerApplication::getApp().lookAndFeel.getCurrentColourScheme());
  91. lf->setupColours();
  92. }
  93. //==============================================================================
  94. void buildProps()
  95. {
  96. PropertyListBuilder builder;
  97. builder.add (new TextPropertyComponent (nameValue, "Name", 256, false),
  98. "The name of your JUCE project.");
  99. builder.add (new TextPropertyComponent (versionValue, "Version", 16, false),
  100. "This will be used for the \"Project Version\" field in the Projucer.");
  101. builder.add (new TextPropertyComponent (vendorValue, "Vendor", 2048, false),
  102. "This will be used for the \"Company Name\" field in the Projucer.");
  103. builder.add (new TextPropertyComponent (websiteValue, "Website", 2048, false),
  104. "This will be used for the \"Company Website\" field in the Projucer");
  105. builder.add (new TextPropertyComponent (descriptionValue, "Description", 2048, true),
  106. "A short description of your JUCE project.");
  107. {
  108. Array<var> moduleVars;
  109. for (auto& m : getJUCEModules())
  110. moduleVars.add (m);
  111. builder.add (new MultiChoicePropertyComponent (dependenciesValue, "Dependencies",
  112. getJUCEModules(), moduleVars),
  113. "The JUCE modules that should be added to your project.");
  114. }
  115. {
  116. Array<var> exporterVars;
  117. StringArray exporterNames;
  118. for (auto& exporterTypeInfo : ProjectExporter::getExporterTypeInfos())
  119. {
  120. exporterVars.add (exporterTypeInfo.identifier.toString());
  121. exporterNames.add (exporterTypeInfo.displayName);
  122. }
  123. builder.add (new MultiChoicePropertyComponent (exportersValue, "Exporters", exporterNames, exporterVars),
  124. "The exporters that should be added to your project.");
  125. }
  126. builder.add (new TextPropertyComponent (moduleFlagsValue, "Module Flags", 2048, true),
  127. "Use this to set one, or many, of the JUCE module flags");
  128. builder.add (new TextPropertyComponent (definesValue, "Defines", 2048, true),
  129. "This sets some global preprocessor definitions for your project. Used to populate the \"Preprocessor Definitions\" field in the Projucer.");
  130. builder.add (new ChoicePropertyComponent (typeValue, "Type",
  131. { "Component", "Plugin", "Console Application" },
  132. { "Component", "AudioProcessor", "Console" }),
  133. "The project type.");
  134. builder.add (new TextPropertyComponent (mainClassValue, "Main Class", 2048, false),
  135. "The name of the main class that should be instantiated. "
  136. "There can only be one main class and it must have a default constructor. "
  137. "Depending on the type, this may need to inherit from a specific JUCE class");
  138. builder.add (new ChoicePropertyComponent (useLocalCopyValue, "Use Local Copy"),
  139. "Enable this to specify that the PIP file should be copied to the generated project directory instead of just referred to.");
  140. propertyGroup.setProperties (builder);
  141. }
  142. //==============================================================================
  143. void valueTreePropertyChanged (ValueTree&, const Identifier& identifier) override
  144. {
  145. if (identifier == Ids::type)
  146. {
  147. auto type = typeValue.get().toString();
  148. if (type == "Component")
  149. {
  150. nameValue.setDefault ("MyComponentPIP");
  151. dependenciesValue.setDefault (getModulesRequiredForComponent());
  152. mainClassValue.setDefault ("MyComponent");
  153. }
  154. else if (type == "AudioProcessor")
  155. {
  156. nameValue.setDefault ("MyPluginPIP");
  157. dependenciesValue.setDefault (getModulesRequiredForAudioProcessor());
  158. mainClassValue.setDefault ("MyPlugin");
  159. }
  160. else if (type == "Console")
  161. {
  162. nameValue.setDefault ("MyConsolePIP");
  163. dependenciesValue.setDefault (getModulesRequiredForConsole());
  164. mainClassValue.setDefault ({});
  165. }
  166. MessageManager::callAsync ([this]
  167. {
  168. buildProps();
  169. resized();
  170. });
  171. }
  172. }
  173. //==============================================================================
  174. String getFormattedMetadataString() const noexcept
  175. {
  176. StringArray metadata;
  177. {
  178. StringArray section;
  179. if (nameValue.get().toString().isNotEmpty()) section.add (" name: " + nameValue.get().toString());
  180. if (versionValue.get().toString().isNotEmpty()) section.add (" version: " + versionValue.get().toString());
  181. if (vendorValue.get().toString().isNotEmpty()) section.add (" vendor: " + vendorValue.get().toString());
  182. if (websiteValue.get().toString().isNotEmpty()) section.add (" website: " + websiteValue.get().toString());
  183. if (descriptionValue.get().toString().isNotEmpty()) section.add (" description: " + descriptionValue.get().toString());
  184. if (! section.isEmpty())
  185. metadata.add (section.joinIntoString (getPreferredLineFeed()));
  186. }
  187. {
  188. StringArray section;
  189. auto dependenciesString = getWidthLimitedStringFromVarArray (dependenciesValue.get());
  190. if (dependenciesString.isNotEmpty()) section.add (" dependencies: " + dependenciesString);
  191. auto exportersString = getWidthLimitedStringFromVarArray (exportersValue.get());
  192. if (exportersString.isNotEmpty()) section.add (" exporters: " + exportersString);
  193. if (! section.isEmpty())
  194. metadata.add (section.joinIntoString (getPreferredLineFeed()));
  195. }
  196. {
  197. StringArray section;
  198. if (moduleFlagsValue.get().toString().isNotEmpty()) section.add (" moduleFlags: " + moduleFlagsValue.get().toString());
  199. if (definesValue.get().toString().isNotEmpty()) section.add (" defines: " + definesValue.get().toString());
  200. if (! section.isEmpty())
  201. metadata.add (section.joinIntoString (getPreferredLineFeed()));
  202. }
  203. {
  204. StringArray section;
  205. if (typeValue.get().toString().isNotEmpty()) section.add (" type: " + typeValue.get().toString());
  206. if (mainClassValue.get().toString().isNotEmpty()) section.add (" mainClass: " + mainClassValue.get().toString());
  207. if (! section.isEmpty())
  208. metadata.add (section.joinIntoString (getPreferredLineFeed()));
  209. }
  210. {
  211. StringArray section;
  212. if (useLocalCopyValue.get()) section.add (" useLocalCopy: " + useLocalCopyValue.get().toString());
  213. if (! section.isEmpty())
  214. metadata.add (section.joinIntoString (getPreferredLineFeed()));
  215. }
  216. return metadata.joinIntoString (String (getPreferredLineFeed()) + getPreferredLineFeed());
  217. }
  218. void createPIPFile (File fileToSave)
  219. {
  220. String fileTemplate (BinaryData::jucer_PIPTemplate_h);
  221. fileTemplate = fileTemplate.replace ("%%pip_metadata%%", getFormattedMetadataString());
  222. auto type = typeValue.get().toString();
  223. if (type == "Component")
  224. {
  225. String componentCode (BinaryData::jucer_ContentCompSimpleTemplate_h);
  226. componentCode = componentCode.substring (componentCode.indexOf ("class %%content_component_class%%"))
  227. .replace ("%%content_component_class%%", mainClassValue.get().toString());
  228. fileTemplate = fileTemplate.replace ("%%pip_code%%", componentCode);
  229. }
  230. else if (type == "AudioProcessor")
  231. {
  232. String audioProcessorCode (BinaryData::jucer_PIPAudioProcessorTemplate_h);
  233. audioProcessorCode = audioProcessorCode.replace ("%%class_name%%", mainClassValue.get().toString())
  234. .replace ("%%name%%", nameValue.get().toString());
  235. fileTemplate = fileTemplate.replace ("%%pip_code%%", audioProcessorCode);
  236. }
  237. else if (type == "Console")
  238. {
  239. String consoleCode (BinaryData::jucer_MainConsoleAppTemplate_cpp);
  240. consoleCode = consoleCode.substring (consoleCode.indexOf ("int main (int argc, char* argv[])"));
  241. fileTemplate = fileTemplate.replace ("%%pip_code%%", consoleCode);
  242. }
  243. if (fileToSave.create())
  244. fileToSave.replaceWithText (fileTemplate);
  245. }
  246. //==============================================================================
  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, StringArray (ProjectExporter::getCurrentPlatformExporterTypeInfo().identifier.toString()), "," },
  255. moduleFlagsValue { pipTree, Ids::moduleFlags, nullptr, "JUCE_STRICT_REFCOUNTEDPOINTER=1" },
  256. definesValue { pipTree, Ids::defines, nullptr },
  257. typeValue { pipTree, Ids::type, nullptr, "Component" },
  258. mainClassValue { pipTree, Ids::mainClass, nullptr, "MyComponent" },
  259. useLocalCopyValue { pipTree, Ids::useLocalCopy, nullptr, false };
  260. std::unique_ptr<PIPCreatorLookAndFeel> lf;
  261. Viewport propertyViewport;
  262. PropertyGroupComponent propertyGroup { "PIP Creator", { getIcons().juceLogo, Colours::transparentBlack } };
  263. TextButton createButton { "Create PIP" };
  264. //==============================================================================
  265. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PIPCreatorWindowComponent)
  266. };