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.

499 lines
16KB

  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. class ModulesFolderPathBox : public Component,
  21. private ButtonListener,
  22. private ComboBoxListener
  23. {
  24. public:
  25. ModulesFolderPathBox (File initialFileOrDirectory)
  26. : currentPathBox ("currentPathBox"),
  27. openFolderButton (TRANS("...")),
  28. modulesLabel (String(), TRANS("Modules Folder") + ":")
  29. {
  30. if (initialFileOrDirectory == File())
  31. initialFileOrDirectory = findDefaultModulesFolder();
  32. setModulesFolder (initialFileOrDirectory);
  33. addAndMakeVisible (currentPathBox);
  34. currentPathBox.setEditableText (true);
  35. currentPathBox.addListener (this);
  36. addAndMakeVisible (openFolderButton);
  37. openFolderButton.addListener (this);
  38. openFolderButton.setTooltip (TRANS ("Select JUCE modules folder"));
  39. addAndMakeVisible (modulesLabel);
  40. modulesLabel.attachToComponent (&currentPathBox, true);
  41. }
  42. void resized() override
  43. {
  44. auto r = getLocalBounds();
  45. openFolderButton.setBounds (r.removeFromRight (30));
  46. modulesLabel.setBounds (r.removeFromLeft (110));
  47. currentPathBox.setBounds (r);
  48. }
  49. static bool selectJuceFolder (File& result)
  50. {
  51. for (;;)
  52. {
  53. FileChooser fc ("Select your JUCE modules folder...",
  54. findDefaultModulesFolder(),
  55. "*");
  56. if (! fc.browseForDirectory())
  57. return false;
  58. if (isJuceModulesFolder (fc.getResult()))
  59. {
  60. result = fc.getResult();
  61. return true;
  62. }
  63. AlertWindow::showMessageBox (AlertWindow::WarningIcon,
  64. "Not a valid JUCE modules folder!",
  65. "Please select the folder containing your juce_* modules!\n\n"
  66. "This is required so that the new project can be given some essential core modules.");
  67. }
  68. }
  69. void selectJuceFolder()
  70. {
  71. File result;
  72. if (selectJuceFolder (result))
  73. setModulesFolder (result);
  74. }
  75. void setModulesFolder (const File& newFolder)
  76. {
  77. if (modulesFolder != newFolder)
  78. {
  79. modulesFolder = newFolder;
  80. currentPathBox.setText (modulesFolder.getFullPathName(), dontSendNotification);
  81. }
  82. }
  83. void buttonClicked (Button*) override
  84. {
  85. selectJuceFolder();
  86. }
  87. void comboBoxChanged (ComboBox*) override
  88. {
  89. setModulesFolder (File::getCurrentWorkingDirectory().getChildFile (currentPathBox.getText()));
  90. }
  91. File modulesFolder;
  92. private:
  93. ComboBox currentPathBox;
  94. TextButton openFolderButton;
  95. Label modulesLabel;
  96. };
  97. /** The target platforms chooser for the chosen template. */
  98. class PlatformTargetsComp : public Component,
  99. private ListBoxModel
  100. {
  101. public:
  102. PlatformTargetsComp()
  103. {
  104. setOpaque (false);
  105. const Array<ProjectExporter::ExporterTypeInfo> types (ProjectExporter::getExporterTypes());
  106. for (auto& type : types)
  107. {
  108. platforms.add (new PlatformType { type.getIcon(), type.name });
  109. addAndMakeVisible (toggles.add (new ToggleButton (String())));
  110. }
  111. listBox.setRowHeight (30);
  112. listBox.setModel (this);
  113. listBox.setOpaque (false);
  114. listBox.setMultipleSelectionEnabled (true);
  115. listBox.setClickingTogglesRowSelection (true);
  116. listBox.setColour (ListBox::backgroundColourId, Colours::transparentBlack);
  117. addAndMakeVisible (listBox);
  118. selectDefaultExporterIfNoneSelected();
  119. }
  120. StringArray getSelectedPlatforms() const
  121. {
  122. StringArray list;
  123. for (int i = 0; i < platforms.size(); ++i)
  124. if (listBox.isRowSelected (i))
  125. list.add (platforms.getUnchecked(i)->name);
  126. return list;
  127. }
  128. void selectDefaultExporterIfNoneSelected()
  129. {
  130. if (listBox.getNumSelectedRows() == 0)
  131. {
  132. for (int i = platforms.size(); --i >= 0;)
  133. {
  134. if (platforms.getUnchecked(i)->name == ProjectExporter::getCurrentPlatformExporterName())
  135. {
  136. listBox.selectRow (i);
  137. break;
  138. }
  139. }
  140. }
  141. }
  142. void resized() override
  143. {
  144. listBox.setBounds (getLocalBounds());
  145. }
  146. int getNumRows() override
  147. {
  148. return platforms.size();
  149. }
  150. void paintListBoxItem (int rowNumber, Graphics& g, int width, int height, bool rowIsSelected) override
  151. {
  152. ignoreUnused (width);
  153. if (auto* platform = platforms[rowNumber])
  154. {
  155. auto bounds = getLocalBounds().withHeight (height).withTrimmedBottom (1);
  156. g.setColour (findColour (rowNumber % 2 == 0 ? widgetBackgroundColourId
  157. : secondaryWidgetBackgroundColourId));
  158. g.fillRect (bounds);
  159. bounds.removeFromLeft (10);
  160. auto toggleBounds = bounds.removeFromLeft (height);
  161. drawToggle (g, toggleBounds, rowIsSelected);
  162. auto iconBounds = bounds.removeFromLeft (height).reduced (5);
  163. g.drawImageWithin (platform->icon, iconBounds.getX(), iconBounds.getY(), iconBounds.getWidth(),
  164. iconBounds.getHeight(), RectanglePlacement::fillDestination);
  165. bounds.removeFromLeft (10);
  166. g.setColour (findColour (widgetTextColourId));
  167. g.drawFittedText (platform->name, bounds, Justification::centredLeft, 1);
  168. }
  169. }
  170. void selectedRowsChanged (int) override
  171. {
  172. selectDefaultExporterIfNoneSelected();
  173. }
  174. private:
  175. struct PlatformType
  176. {
  177. Image icon;
  178. String name;
  179. };
  180. void drawToggle (Graphics& g, Rectangle<int> bounds, bool isToggled)
  181. {
  182. auto sideLength = jmin (bounds.getWidth(), bounds.getHeight());
  183. bounds = bounds.withSizeKeepingCentre (sideLength, sideLength).reduced (4);
  184. g.setColour (findColour (ToggleButton::tickDisabledColourId));
  185. g.drawRoundedRectangle (bounds.toFloat(), 2.0f, 1.0f);
  186. if (isToggled)
  187. {
  188. g.setColour (findColour (ToggleButton::tickColourId));
  189. const auto tick = getTickShape (0.75f);
  190. g.fillPath (tick, tick.getTransformToScaleToFit (bounds.reduced (4, 5).toFloat(), false));
  191. }
  192. }
  193. Path getTickShape (float height)
  194. {
  195. static const unsigned char pathData[] = { 110,109,32,210,202,64,126,183,148,64,108,39,244,247,64,245,76,124,64,108,178,131,27,65,246,76,252,64,108,175,242,4,65,246,76,252,
  196. 64,108,236,5,68,65,0,0,160,180,108,240,150,90,65,21,136,52,63,108,48,59,16,65,0,0,32,65,108,32,210,202,64,126,183,148,64, 99,101,0,0 };
  197. Path path;
  198. path.loadPathFromData (pathData, sizeof (pathData));
  199. path.scaleToFit (0, 0, height * 2.0f, height, true);
  200. return path;
  201. }
  202. ListBox listBox;
  203. OwnedArray<PlatformType> platforms;
  204. OwnedArray<ToggleButton> toggles;
  205. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PlatformTargetsComp)
  206. };
  207. //==============================================================================
  208. /**
  209. The Component for project creation.
  210. Features a file browser to select project destination and
  211. a list box of platform targets to generate.
  212. */
  213. class WizardComp : public Component,
  214. private ButtonListener,
  215. private ComboBoxListener,
  216. private TextEditorListener,
  217. private FileBrowserListener
  218. {
  219. public:
  220. WizardComp()
  221. : platformTargets(),
  222. projectName (TRANS("Project name")),
  223. modulesPathBox (findDefaultModulesFolder())
  224. {
  225. setOpaque (false);
  226. addChildAndSetID (&projectName, "projectName");
  227. projectName.setText ("NewProject");
  228. nameLabel.attachToComponent (&projectName, true);
  229. projectName.addListener (this);
  230. addChildAndSetID (&projectType, "projectType");
  231. projectType.addItemList (getWizardNames(), 1);
  232. projectType.setSelectedId (1, dontSendNotification);
  233. typeLabel.attachToComponent (&projectType, true);
  234. projectType.addListener (this);
  235. addChildAndSetID (&fileOutline, "fileOutline");
  236. fileOutline.setColour (GroupComponent::outlineColourId, Colours::black.withAlpha (0.2f));
  237. fileOutline.setTextLabelPosition (Justification::centred);
  238. addChildAndSetID (&targetsOutline, "targetsOutline");
  239. targetsOutline.setColour (GroupComponent::outlineColourId, Colours::black.withAlpha (0.2f));
  240. targetsOutline.setTextLabelPosition (Justification::centred);
  241. addChildAndSetID (&platformTargets, "platformTargets");
  242. addChildAndSetID (&fileBrowser, "fileBrowser");
  243. fileBrowser.setFilenameBoxLabel ("Folder:");
  244. fileBrowser.setFileName (File::createLegalFileName (projectName.getText()));
  245. fileBrowser.addListener (this);
  246. addChildAndSetID (&createButton, "createButton");
  247. createButton.addListener (this);
  248. addChildAndSetID (&cancelButton, "cancelButton");
  249. cancelButton.addShortcut (KeyPress (KeyPress::escapeKey));
  250. cancelButton.addListener (this);
  251. addChildAndSetID (&modulesPathBox, "modulesPathBox");
  252. addChildAndSetID (&filesToCreate, "filesToCreate");
  253. filesToCreateLabel.attachToComponent (&filesToCreate, true);
  254. updateFileCreationTypes();
  255. updateCreateButton();
  256. lookAndFeelChanged();
  257. }
  258. void paint (Graphics& g) override
  259. {
  260. g.fillAll (findColour (backgroundColourId));
  261. }
  262. void resized() override
  263. {
  264. auto r = getLocalBounds();
  265. auto left = r.removeFromLeft (getWidth() / 2).reduced (15);
  266. auto right = r.reduced (15);
  267. projectName.setBounds (left.removeFromTop (22).withTrimmedLeft (120));
  268. left.removeFromTop (20);
  269. projectType.setBounds (left.removeFromTop (22).withTrimmedLeft (120));
  270. left.removeFromTop (20);
  271. fileOutline.setBounds (left);
  272. fileBrowser.setBounds (left.reduced (25));
  273. auto buttons = right.removeFromBottom (30);
  274. right.removeFromBottom (10);
  275. createButton.setBounds (buttons.removeFromRight (130));
  276. buttons.removeFromRight (10);
  277. cancelButton.setBounds (buttons.removeFromRight (130));
  278. filesToCreate.setBounds (right.removeFromTop (22).withTrimmedLeft (150));
  279. right.removeFromTop (20);
  280. modulesPathBox.setBounds (right.removeFromTop (22));
  281. right.removeFromTop (20);
  282. targetsOutline.setBounds (right);
  283. platformTargets.setBounds (right.reduced (25));
  284. }
  285. void buttonClicked (Button* b) override
  286. {
  287. if (b == &createButton)
  288. {
  289. createProject();
  290. }
  291. else if (b == &cancelButton)
  292. {
  293. returnToTemplatesPage();
  294. }
  295. }
  296. void returnToTemplatesPage()
  297. {
  298. if (auto* parent = findParentComponentOfClass<SlidingPanelComponent>())
  299. {
  300. if (parent->getNumTabs() > 0)
  301. parent->goToTab (parent->getCurrentTabIndex() - 1);
  302. }
  303. else
  304. {
  305. jassertfalse;
  306. }
  307. }
  308. void createProject()
  309. {
  310. auto* mw = Component::findParentComponentOfClass<MainWindow>();
  311. jassert (mw != nullptr);
  312. if (ScopedPointer<NewProjectWizardClasses::NewProjectWizard> wizard = createWizard())
  313. {
  314. Result result (wizard->processResultsFromSetupItems (*this));
  315. if (result.failed())
  316. {
  317. AlertWindow::showMessageBox (AlertWindow::WarningIcon,
  318. TRANS("Create Project"),
  319. result.getErrorMessage());
  320. return;
  321. }
  322. wizard->modulesFolder = modulesPathBox.modulesFolder;
  323. if (! isJuceModulesFolder (wizard->modulesFolder))
  324. if (! wizard->selectJuceFolder())
  325. return;
  326. if (ScopedPointer<Project> project = wizard->runWizard (*this, projectName.getText(),
  327. fileBrowser.getSelectedFile (0)))
  328. mw->setProject (project.release());
  329. }
  330. }
  331. void updateFileCreationTypes()
  332. {
  333. StringArray items;
  334. if (ScopedPointer<NewProjectWizardClasses::NewProjectWizard> wizard = createWizard())
  335. items = wizard->getFileCreationOptions();
  336. filesToCreate.clear();
  337. filesToCreate.addItemList (items, 1);
  338. filesToCreate.setSelectedId (1, dontSendNotification);
  339. }
  340. void comboBoxChanged (ComboBox*) override
  341. {
  342. updateFileCreationTypes();
  343. }
  344. void textEditorTextChanged (TextEditor&) override
  345. {
  346. updateCreateButton();
  347. fileBrowser.setFileName (File::createLegalFileName (projectName.getText()));
  348. }
  349. void selectionChanged() override {}
  350. void fileClicked (const File&, const MouseEvent&) override {}
  351. void fileDoubleClicked (const File&) override {}
  352. void browserRootChanged (const File&) override
  353. {
  354. fileBrowser.setFileName (File::createLegalFileName (projectName.getText()));
  355. }
  356. int getFileCreationComboID() const
  357. {
  358. return filesToCreate.getSelectedItemIndex();
  359. }
  360. ComboBox projectType, filesToCreate;
  361. PlatformTargetsComp platformTargets;
  362. private:
  363. TextEditor projectName;
  364. Label nameLabel { {}, TRANS("Project Name") + ":" };
  365. Label typeLabel { {}, TRANS("Project Type") + ":" };
  366. Label filesToCreateLabel { {}, TRANS("Files to Auto-Generate") + ":" };
  367. FileBrowserComponent fileBrowser { FileBrowserComponent::saveMode
  368. | FileBrowserComponent::canSelectDirectories
  369. | FileBrowserComponent::doNotClearFileNameOnRootChange,
  370. NewProjectWizardClasses::getLastWizardFolder(), nullptr, nullptr };
  371. GroupComponent fileOutline { {}, TRANS("Project Folder") + ":" };
  372. GroupComponent targetsOutline { {}, TRANS("Target Platforms") + ":" };
  373. TextButton createButton { TRANS("Create") + "..." };
  374. TextButton cancelButton { TRANS("Cancel") };
  375. ModulesFolderPathBox modulesPathBox;
  376. NewProjectWizardClasses::NewProjectWizard* createWizard()
  377. {
  378. return createWizardType (projectType.getSelectedItemIndex());
  379. }
  380. void updateCreateButton()
  381. {
  382. createButton.setEnabled (projectName.getText().trim().isNotEmpty());
  383. }
  384. void lookAndFeelChanged() override
  385. {
  386. projectName.setColour (TextEditor::backgroundColourId, findColour (backgroundColourId));
  387. projectName.setColour (TextEditor::textColourId, findColour (defaultTextColourId));
  388. projectName.setColour (TextEditor::outlineColourId, findColour (defaultTextColourId));
  389. projectName.applyFontToAllText (projectName.getFont());
  390. fileBrowser.resized();
  391. }
  392. };