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.

516 lines
19KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2013 - Raw Material Software Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. #ifndef __JUCER_MODULESPANEL_JUCEHEADER__
  18. #define __JUCER_MODULESPANEL_JUCEHEADER__
  19. class ModulesPanel : public PropertyComponent,
  20. public FilenameComponentListener,
  21. public ButtonListener
  22. {
  23. public:
  24. ModulesPanel (Project& p)
  25. : PropertyComponent ("Modules", 500),
  26. project (p),
  27. modulesLocation ("modules", ModuleList::getLocalModulesFolder (&project),
  28. true, true, false, "*", String::empty,
  29. "Select a folder containing your JUCE modules..."),
  30. modulesLabel (String::empty, "Module source folder:"),
  31. updateModulesButton ("Check for module updates..."),
  32. moduleListBox (moduleList),
  33. copyingMessage (p, moduleList)
  34. {
  35. moduleList.rescan (ModuleList::getLocalModulesFolder (&project));
  36. addAndMakeVisible (&modulesLocation);
  37. modulesLocation.addListener (this);
  38. modulesLabel.attachToComponent (&modulesLocation, true);
  39. addAndMakeVisible (&updateModulesButton);
  40. updateModulesButton.addListener (this);
  41. moduleListBox.setOwner (this);
  42. addAndMakeVisible (&moduleListBox);
  43. addAndMakeVisible (&copyingMessage);
  44. copyingMessage.refresh();
  45. }
  46. void filenameComponentChanged (FilenameComponent*)
  47. {
  48. moduleList.rescan (modulesLocation.getCurrentFile());
  49. modulesLocation.setCurrentFile (moduleList.getModulesFolder(), false, dontSendNotification);
  50. ModuleList::setLocalModulesFolder (moduleList.getModulesFolder());
  51. moduleListBox.refresh();
  52. }
  53. void buttonClicked (Button*)
  54. {
  55. JuceUpdater::show (moduleList, getTopLevelComponent(), "");
  56. filenameComponentChanged (nullptr);
  57. }
  58. bool isEnabled (const ModuleList::Module* m) const
  59. {
  60. return project.isModuleEnabled (m->uid);
  61. }
  62. void setEnabled (const ModuleList::Module* m, bool enable)
  63. {
  64. if (enable)
  65. project.addModule (m->uid, true);
  66. else
  67. project.removeModule (m->uid);
  68. refresh();
  69. }
  70. bool areDependenciesMissing (const ModuleList::Module* m)
  71. {
  72. return moduleList.getExtraDependenciesNeeded (project, *m).size() > 0;
  73. }
  74. void selectionChanged (const ModuleList::Module* selectedModule)
  75. {
  76. settings = nullptr;
  77. if (selectedModule != nullptr)
  78. addAndMakeVisible (settings = new ModuleSettingsPanel (project, moduleList, selectedModule->uid));
  79. copyingMessage.refresh();
  80. resized();
  81. }
  82. void refresh()
  83. {
  84. moduleListBox.refresh();
  85. if (settings != nullptr)
  86. settings->refreshAll();
  87. copyingMessage.refresh();
  88. }
  89. void paint (Graphics& g) // (overridden to avoid drawing the name)
  90. {
  91. getLookAndFeel().drawPropertyComponentBackground (g, getWidth(), getHeight(), *this);
  92. }
  93. void resized()
  94. {
  95. modulesLocation.setBounds (150, 3, getWidth() - 180 - 150, 25);
  96. updateModulesButton.setBounds (modulesLocation.getRight() + 6, 3, getWidth() - modulesLocation.getRight() - 12, 25);
  97. moduleListBox.setBounds (5, 34, getWidth() / 3, getHeight() - 72);
  98. copyingMessage.setBounds (5, moduleListBox.getBottom() + 2, getWidth() - 10, getHeight() - moduleListBox.getBottom() - 4);
  99. if (settings != nullptr)
  100. settings->setBounds (moduleListBox.getRight() + 5, moduleListBox.getY(),
  101. getWidth() - moduleListBox.getRight() - 9, moduleListBox.getHeight());
  102. }
  103. //==============================================================================
  104. class ModuleSelectionListBox : public ListBox,
  105. public ListBoxModel
  106. {
  107. public:
  108. ModuleSelectionListBox (ModuleList& ml)
  109. : list (ml), owner (nullptr)
  110. {
  111. setColour (ListBox::backgroundColourId, Colours::white.withAlpha (0.4f));
  112. setTooltip ("Use this list to select which modules should be included in your app.\n"
  113. "Any modules which have missing dependencies will be shown in red.");
  114. }
  115. void setOwner (ModulesPanel* newOwner)
  116. {
  117. owner = newOwner;
  118. setModel (this);
  119. }
  120. void refresh()
  121. {
  122. updateContent();
  123. repaint();
  124. }
  125. int getNumRows()
  126. {
  127. return list.modules.size();
  128. }
  129. void paintListBoxItem (int rowNumber, Graphics& g, int width, int height, bool rowIsSelected)
  130. {
  131. if (rowIsSelected)
  132. g.fillAll (findColour (TextEditor::highlightColourId));
  133. if (const ModuleList::Module* const m = list.modules [rowNumber])
  134. {
  135. const float tickSize = height * 0.7f;
  136. getLookAndFeel().drawTickBox (g, *this, (height - tickSize) / 2, (height - tickSize) / 2, tickSize, tickSize,
  137. owner->isEnabled (m), true, false, false);
  138. if (owner->isEnabled (m) && owner->areDependenciesMissing (m))
  139. g.setColour (Colours::red);
  140. else
  141. g.setColour (Colours::black);
  142. g.setFont (Font (height * 0.7f, Font::bold));
  143. g.drawFittedText (m->uid, height, 0, width - height, height, Justification::centredLeft, 1);
  144. }
  145. }
  146. void listBoxItemClicked (int row, const MouseEvent& e)
  147. {
  148. if (e.x < getRowHeight())
  149. flipRow (row);
  150. }
  151. void listBoxItemDoubleClicked (int row, const MouseEvent&)
  152. {
  153. flipRow (row);
  154. }
  155. void returnKeyPressed (int row)
  156. {
  157. flipRow (row);
  158. }
  159. void selectedRowsChanged (int row)
  160. {
  161. owner->selectionChanged (list.modules [row]);
  162. }
  163. void flipRow (int row)
  164. {
  165. if (const ModuleList::Module* const m = list.modules [row])
  166. owner->setEnabled (m, ! owner->isEnabled (m));
  167. }
  168. private:
  169. ModuleList& list;
  170. ModulesPanel* owner;
  171. };
  172. //==============================================================================
  173. class ModuleSettingsPanel : public PropertyPanel
  174. {
  175. public:
  176. ModuleSettingsPanel (Project& p, ModuleList& list, const String& modID)
  177. : project (p), moduleList (list), moduleID (modID)
  178. {
  179. refreshAll();
  180. }
  181. void refreshAll()
  182. {
  183. setEnabled (project.isModuleEnabled (moduleID));
  184. clear();
  185. PropertyListBuilder props;
  186. ScopedPointer<LibraryModule> module (moduleList.loadModule (moduleID));
  187. if (module != nullptr)
  188. {
  189. props.add (new ModuleInfoComponent (project, moduleList, moduleID));
  190. if (project.isModuleEnabled (moduleID))
  191. {
  192. if (const ModuleList::Module* m = moduleList.findModuleInfo (moduleID))
  193. if (moduleList.getExtraDependenciesNeeded (project, *m).size() > 0)
  194. props.add (new MissingDependenciesComponent (project, moduleList, moduleID));
  195. }
  196. props.add (new BooleanPropertyComponent (project.shouldShowAllModuleFilesInProject (moduleID),
  197. "Add source to project", "Make module files browsable in projects"),
  198. "If this is enabled, then the entire source tree from this module will be shown inside your project, "
  199. "making it easy to browse/edit the module's classes. If disabled, then only the minimum number of files "
  200. "required to compile it will appear inside your project.");
  201. props.add (new BooleanPropertyComponent (project.shouldCopyModuleFilesLocally (moduleID),
  202. "Create local copy", "Copy the module into the project folder"),
  203. "If this is enabled, then a local copy of the entire module will be made inside your project (in the auto-generated JuceLibraryFiles folder), "
  204. "so that your project will be self-contained, and won't need to contain any references to files in other folders. "
  205. "This also means that you can check the module into your source-control system to make sure it is always in sync with your own code.");
  206. StringArray possibleValues;
  207. possibleValues.add ("(Use Default)");
  208. possibleValues.add ("Enabled");
  209. possibleValues.add ("Disabled");
  210. Array<var> mappings;
  211. mappings.add (Project::configFlagDefault);
  212. mappings.add (Project::configFlagEnabled);
  213. mappings.add (Project::configFlagDisabled);
  214. OwnedArray <Project::ConfigFlag> configFlags;
  215. module->getConfigFlags (project, configFlags);
  216. for (int i = 0; i < configFlags.size(); ++i)
  217. {
  218. ChoicePropertyComponent* c = new ChoicePropertyComponent (configFlags[i]->value,
  219. configFlags[i]->symbol,
  220. possibleValues, mappings);
  221. c->setTooltip (configFlags[i]->description);
  222. props.add (c);
  223. }
  224. }
  225. addProperties (props.components);
  226. }
  227. private:
  228. Project& project;
  229. ModuleList& moduleList;
  230. String moduleID;
  231. //==============================================================================
  232. class ModuleInfoComponent : public PropertyComponent
  233. {
  234. public:
  235. ModuleInfoComponent (Project& p, ModuleList& list, const String& modID)
  236. : PropertyComponent ("Module", 100), project (p), moduleList (list), moduleID (modID)
  237. {
  238. }
  239. void refresh() {}
  240. void paint (Graphics& g)
  241. {
  242. g.setColour (Colours::white.withAlpha (0.4f));
  243. g.fillRect (0, 0, getWidth(), getHeight() - 1);
  244. if (const ModuleList::Module* module = moduleList.findModuleInfo (moduleID))
  245. {
  246. AttributedString s;
  247. s.setJustification (Justification::topLeft);
  248. Font f (13.0f);
  249. s.append (module->name + "\n", f.boldened());
  250. s.append ("Version: " + module->version
  251. + " License: " + module->license + "\n", f.italicised());
  252. s.append ("\n" + module->description, f);
  253. s.draw (g, getLocalBounds().reduced (4, 2).toFloat());
  254. }
  255. }
  256. private:
  257. Project& project;
  258. ModuleList& moduleList;
  259. String moduleID;
  260. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ModuleInfoComponent)
  261. };
  262. //==============================================================================
  263. class MissingDependenciesComponent : public PropertyComponent,
  264. public ButtonListener
  265. {
  266. public:
  267. MissingDependenciesComponent (Project& p, ModuleList& list, const String& modID)
  268. : PropertyComponent ("Dependencies", 100),
  269. project (p), moduleList (list), moduleID (modID),
  270. fixButton ("Enable Required Modules")
  271. {
  272. if (const ModuleList::Module* module = moduleList.findModuleInfo (moduleID))
  273. missingDependencies = moduleList.getExtraDependenciesNeeded (project, *module);
  274. addAndMakeVisible (&fixButton);
  275. fixButton.setColour (TextButton::buttonColourId, Colours::red);
  276. fixButton.setColour (TextButton::textColourOffId, Colours::white);
  277. fixButton.addListener (this);
  278. }
  279. void refresh() {}
  280. void paint (Graphics& g)
  281. {
  282. g.setColour (Colours::white.withAlpha (0.4f));
  283. g.fillRect (0, 0, getWidth(), getHeight() - 1);
  284. String text ("This module requires the following dependencies:\n");
  285. text << missingDependencies.joinIntoString (", ");
  286. AttributedString s;
  287. s.setJustification (Justification::topLeft);
  288. s.append (text, Font (13.0f), Colours::red);
  289. s.draw (g, getLocalBounds().reduced (4, 16).toFloat());
  290. }
  291. void buttonClicked (Button*)
  292. {
  293. bool isModuleCopiedLocally = project.shouldCopyModuleFilesLocally (moduleID).getValue();
  294. for (int i = missingDependencies.size(); --i >= 0;)
  295. project.addModule (missingDependencies[i], isModuleCopiedLocally);
  296. if (ModulesPanel* mp = findParentComponentOfClass<ModulesPanel>())
  297. mp->refresh();
  298. }
  299. void resized()
  300. {
  301. fixButton.setBounds (getWidth() - 168, getHeight() - 26, 160, 22);
  302. }
  303. private:
  304. Project& project;
  305. ModuleList& moduleList;
  306. String moduleID;
  307. StringArray missingDependencies;
  308. TextButton fixButton;
  309. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MissingDependenciesComponent)
  310. };
  311. };
  312. //==============================================================================
  313. class ModuleCopyingInfo : public Component,
  314. public ButtonListener,
  315. public Timer
  316. {
  317. public:
  318. ModuleCopyingInfo (Project& p, ModuleList& modules)
  319. : project (p), list (modules),
  320. copyModeButton ("Set Copying Mode...")
  321. {
  322. addAndMakeVisible (&copyModeButton);
  323. copyModeButton.addListener (this);
  324. startTimer (1500);
  325. }
  326. void paint (Graphics& g)
  327. {
  328. g.setFont (11.0f);
  329. g.setColour (Colours::darkred);
  330. g.drawFittedText (getName(), copyModeButton.getRight() + 10, 0,
  331. getWidth() - copyModeButton.getRight() - 16, getHeight(),
  332. Justification::centredRight, 4);
  333. }
  334. void resized()
  335. {
  336. copyModeButton.setBounds (0, getHeight() / 2 - 10, 160, 20);
  337. }
  338. void refresh()
  339. {
  340. int numCopied, numNonCopied;
  341. countCopiedModules (numCopied, numNonCopied);
  342. String newName;
  343. if (numCopied > 0 && numNonCopied > 0)
  344. newName = "Warning! Some of your modules are set to use local copies, and others are using remote references.\n"
  345. "This may create problems if some modules expect to share the same parent folder, so you may "
  346. "want to make sure that they are all either copied or not.";
  347. if (project.isAudioPluginModuleMissing())
  348. newName = "Warning! Your project is an audio plugin, but you haven't enabled the 'juce_audio_plugin_client' module!";
  349. if (newName != getName())
  350. {
  351. setName (newName);
  352. repaint();
  353. }
  354. }
  355. void countCopiedModules (int& numCopied, int& numNonCopied)
  356. {
  357. numCopied = numNonCopied = 0;
  358. for (int i = list.modules.size(); --i >= 0;)
  359. {
  360. const String moduleID (list.modules.getUnchecked(i)->uid);
  361. if (project.isModuleEnabled (moduleID))
  362. {
  363. if (project.shouldCopyModuleFilesLocally (moduleID).getValue())
  364. ++numCopied;
  365. else
  366. ++numNonCopied;
  367. }
  368. }
  369. }
  370. void buttonClicked (Button*)
  371. {
  372. PopupMenu menu;
  373. menu.addItem (1, "Enable local copying for all modules");
  374. menu.addItem (2, "Disable local copying for all modules");
  375. menu.showMenuAsync (PopupMenu::Options().withTargetComponent (&copyModeButton),
  376. ModalCallbackFunction::forComponent (copyMenuItemChosen, this));
  377. }
  378. static void copyMenuItemChosen (int resultCode, ModuleCopyingInfo* comp)
  379. {
  380. if (resultCode > 0 && comp != nullptr)
  381. comp->setCopyModeForAllModules (resultCode == 1);
  382. }
  383. void setCopyModeForAllModules (bool copyEnabled)
  384. {
  385. for (int i = list.modules.size(); --i >= 0;)
  386. {
  387. const String moduleID (list.modules.getUnchecked(i)->uid);
  388. if (project.isModuleEnabled (moduleID))
  389. project.shouldCopyModuleFilesLocally (moduleID) = copyEnabled;
  390. }
  391. refresh();
  392. }
  393. void timerCallback() override
  394. {
  395. refresh();
  396. }
  397. private:
  398. Project& project;
  399. ModuleList& list;
  400. TextButton copyModeButton;
  401. };
  402. private:
  403. Project& project;
  404. ModuleList moduleList;
  405. FilenameComponent modulesLocation;
  406. Label modulesLabel;
  407. TextButton updateModulesButton;
  408. ModuleSelectionListBox moduleListBox;
  409. ModuleCopyingInfo copyingMessage;
  410. ScopedPointer<ModuleSettingsPanel> settings;
  411. };
  412. #endif // __JUCER_MODULESPANEL_JUCEHEADER__