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.

328 lines
14KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE 6 technical preview.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. You may use this code under the terms of the GPL v3
  6. (see www.gnu.org/licenses).
  7. For this technical preview, this file is not subject to commercial licensing.
  8. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  9. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  10. DISCLAIMED.
  11. ==============================================================================
  12. */
  13. #pragma once
  14. //==============================================================================
  15. static String getWidthLimitedStringFromVarArray (const var& varArray) noexcept
  16. {
  17. if (! varArray.isArray())
  18. return {};
  19. int numLines = 1;
  20. const int lineWidth = 100;
  21. const String indent (" ");
  22. String str;
  23. if (auto* arr = varArray.getArray())
  24. {
  25. for (auto& v : *arr)
  26. {
  27. if ((str.length() + v.toString().length()) > (lineWidth * numLines))
  28. {
  29. str += newLine;
  30. str += indent;
  31. ++numLines;
  32. }
  33. str += v.toString() + (arr->indexOf (v) != arr->size() - 1 ? ", " : "");
  34. }
  35. }
  36. return str;
  37. }
  38. //==============================================================================
  39. class PIPCreatorWindowComponent : public Component,
  40. private ValueTree::Listener
  41. {
  42. public:
  43. PIPCreatorWindowComponent()
  44. {
  45. lf.reset (new PIPCreatorLookAndFeel());
  46. setLookAndFeel (lf.get());
  47. addAndMakeVisible (propertyViewport);
  48. propertyViewport.setViewedComponent (&propertyGroup, false);
  49. buildProps();
  50. addAndMakeVisible (createButton);
  51. createButton.onClick = [this]
  52. {
  53. FileChooser fc ("Save PIP File",
  54. File::getSpecialLocation (File::SpecialLocationType::userDesktopDirectory)
  55. .getChildFile (nameValue.get().toString() + ".h"));
  56. fc.browseForFileToSave (true);
  57. createPIPFile (fc.getResult());
  58. };
  59. pipTree.addListener (this);
  60. }
  61. ~PIPCreatorWindowComponent() override
  62. {
  63. setLookAndFeel (nullptr);
  64. }
  65. void resized() override
  66. {
  67. auto bounds = getLocalBounds();
  68. createButton.setBounds (bounds.removeFromBottom (50).reduced (100, 10));
  69. propertyGroup.updateSize (0, 0, getWidth() - propertyViewport.getScrollBarThickness());
  70. propertyViewport.setBounds (bounds);
  71. }
  72. private:
  73. //==============================================================================
  74. struct PIPCreatorLookAndFeel : public ProjucerLookAndFeel
  75. {
  76. PIPCreatorLookAndFeel() {}
  77. Rectangle<int> getPropertyComponentContentPosition (PropertyComponent& component)
  78. {
  79. auto textW = jmin (200, component.getWidth() / 3);
  80. return { textW, 0, component.getWidth() - textW, component.getHeight() - 1 };
  81. }
  82. };
  83. void lookAndFeelChanged() override
  84. {
  85. lf->setColourScheme (ProjucerApplication::getApp().lookAndFeel.getCurrentColourScheme());
  86. lf->setupColours();
  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. //==============================================================================
  166. String getFormattedMetadataString() const noexcept
  167. {
  168. StringArray metadata;
  169. {
  170. StringArray section;
  171. if (nameValue.get().toString().isNotEmpty()) section.add (" name: " + nameValue.get().toString());
  172. if (versionValue.get().toString().isNotEmpty()) section.add (" version: " + versionValue.get().toString());
  173. if (vendorValue.get().toString().isNotEmpty()) section.add (" vendor: " + vendorValue.get().toString());
  174. if (websiteValue.get().toString().isNotEmpty()) section.add (" website: " + websiteValue.get().toString());
  175. if (descriptionValue.get().toString().isNotEmpty()) section.add (" description: " + descriptionValue.get().toString());
  176. if (! section.isEmpty())
  177. metadata.add (section.joinIntoString (getPreferredLineFeed()));
  178. }
  179. {
  180. StringArray section;
  181. auto dependenciesString = getWidthLimitedStringFromVarArray (dependenciesValue.get());
  182. if (dependenciesString.isNotEmpty()) section.add (" dependencies: " + dependenciesString);
  183. auto exportersString = getWidthLimitedStringFromVarArray (exportersValue.get());
  184. if (exportersString.isNotEmpty()) section.add (" exporters: " + exportersString);
  185. if (! section.isEmpty())
  186. metadata.add (section.joinIntoString (getPreferredLineFeed()));
  187. }
  188. {
  189. StringArray section;
  190. if (moduleFlagsValue.get().toString().isNotEmpty()) section.add (" moduleFlags: " + moduleFlagsValue.get().toString());
  191. if (definesValue.get().toString().isNotEmpty()) section.add (" defines: " + definesValue.get().toString());
  192. if (! section.isEmpty())
  193. metadata.add (section.joinIntoString (getPreferredLineFeed()));
  194. }
  195. {
  196. StringArray section;
  197. if (typeValue.get().toString().isNotEmpty()) section.add (" type: " + typeValue.get().toString());
  198. if (mainClassValue.get().toString().isNotEmpty()) section.add (" mainClass: " + mainClassValue.get().toString());
  199. if (! section.isEmpty())
  200. metadata.add (section.joinIntoString (getPreferredLineFeed()));
  201. }
  202. {
  203. StringArray section;
  204. if (useLocalCopyValue.get()) section.add (" useLocalCopy: " + useLocalCopyValue.get().toString());
  205. if (! section.isEmpty())
  206. metadata.add (section.joinIntoString (getPreferredLineFeed()));
  207. }
  208. return metadata.joinIntoString (String (getPreferredLineFeed()) + getPreferredLineFeed());
  209. }
  210. void createPIPFile (File fileToSave)
  211. {
  212. String fileTemplate (BinaryData::jucer_PIPTemplate_h);
  213. fileTemplate = fileTemplate.replace ("%%pip_metadata%%", getFormattedMetadataString());
  214. auto type = typeValue.get().toString();
  215. if (type == "Component")
  216. {
  217. String componentCode (BinaryData::jucer_ContentCompSimpleTemplate_h);
  218. componentCode = componentCode.substring (componentCode.indexOf ("class %%content_component_class%%"))
  219. .replace ("%%content_component_class%%", mainClassValue.get().toString());
  220. fileTemplate = fileTemplate.replace ("%%pip_code%%", componentCode);
  221. }
  222. else if (type == "AudioProcessor")
  223. {
  224. String audioProcessorCode (BinaryData::jucer_PIPAudioProcessorTemplate_h);
  225. audioProcessorCode = audioProcessorCode.replace ("%%class_name%%", mainClassValue.get().toString())
  226. .replace ("%%name%%", nameValue.get().toString());
  227. fileTemplate = fileTemplate.replace ("%%pip_code%%", audioProcessorCode);
  228. }
  229. else if (type == "Console")
  230. {
  231. String consoleCode (BinaryData::jucer_MainConsoleAppTemplate_cpp);
  232. consoleCode = consoleCode.substring (consoleCode.indexOf ("int main (int argc, char* argv[])"));
  233. fileTemplate = fileTemplate.replace ("%%pip_code%%", consoleCode);
  234. }
  235. if (fileToSave.create())
  236. fileToSave.replaceWithText (fileTemplate);
  237. }
  238. //==============================================================================
  239. ValueTree pipTree { "PIPSettings" };
  240. ValueWithDefault nameValue { pipTree, Ids::name, nullptr, "MyComponentPIP" },
  241. versionValue { pipTree, Ids::version, nullptr },
  242. vendorValue { pipTree, Ids::vendor, nullptr },
  243. websiteValue { pipTree, Ids::website, nullptr },
  244. descriptionValue { pipTree, Ids::description, nullptr },
  245. dependenciesValue { pipTree, Ids::dependencies_, nullptr, getModulesRequiredForComponent(), "," },
  246. exportersValue { pipTree, Ids::exporters, nullptr,
  247. StringArray (ProjectExporter::getValueTreeNameForExporter (ProjectExporter::getCurrentPlatformExporterName()).toLowerCase()), "," },
  248. moduleFlagsValue { pipTree, Ids::moduleFlags, nullptr, "JUCE_STRICT_REFCOUNTEDPOINTER=1" },
  249. definesValue { pipTree, Ids::defines, nullptr },
  250. typeValue { pipTree, Ids::type, nullptr, "Component" },
  251. mainClassValue { pipTree, Ids::mainClass, nullptr, "MyComponent" },
  252. useLocalCopyValue { pipTree, Ids::useLocalCopy, nullptr, false };
  253. std::unique_ptr<PIPCreatorLookAndFeel> lf;
  254. Viewport propertyViewport;
  255. PropertyGroupComponent propertyGroup { "PIP Creator", { getIcons().juceLogo, Colours::transparentBlack } };
  256. TextButton createButton { "Create PIP" };
  257. //==============================================================================
  258. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PIPCreatorWindowComponent)
  259. };