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.

348 lines
15KB

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