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.

480 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 (int i = 0; i < types.size(); ++i)
  107. {
  108. const ProjectExporter::ExporterTypeInfo& type = types.getReference (i);
  109. platforms.add (new PlatformType (type.getIcon(), type.name));
  110. addAndMakeVisible (toggles.add (new ToggleButton (String())));
  111. }
  112. listBox.setRowHeight (30);
  113. listBox.setModel (this);
  114. listBox.setOpaque (false);
  115. listBox.setMultipleSelectionEnabled (true);
  116. listBox.setClickingTogglesRowSelection (true);
  117. listBox.setColour (ListBox::backgroundColourId, Colours::transparentBlack);
  118. addAndMakeVisible (listBox);
  119. selectDefaultExporterIfNoneSelected();
  120. }
  121. StringArray getSelectedPlatforms() const
  122. {
  123. StringArray list;
  124. for (int i = 0; i < platforms.size(); ++i)
  125. if (listBox.isRowSelected (i))
  126. list.add (platforms.getUnchecked(i)->name);
  127. return list;
  128. }
  129. void selectDefaultExporterIfNoneSelected()
  130. {
  131. if (listBox.getNumSelectedRows() == 0)
  132. {
  133. for (int i = platforms.size(); --i >= 0;)
  134. {
  135. if (platforms.getUnchecked(i)->name == ProjectExporter::getCurrentPlatformExporterName())
  136. {
  137. listBox.selectRow (i);
  138. break;
  139. }
  140. }
  141. }
  142. }
  143. void resized() override
  144. {
  145. listBox.setBounds (getLocalBounds());
  146. }
  147. int getNumRows() override
  148. {
  149. return platforms.size();
  150. }
  151. void paintListBoxItem (int rowNumber, Graphics& g, int width, int height, bool rowIsSelected) override
  152. {
  153. ignoreUnused (width);
  154. if (PlatformType* platform = platforms[rowNumber])
  155. {
  156. auto bounds = getLocalBounds().withHeight (height).withTrimmedBottom (1);
  157. g.setColour (findColour (rowNumber % 2 == 0 ? widgetBackgroundColourId
  158. : secondaryWidgetBackgroundColourId));
  159. g.fillRect (bounds);
  160. bounds.removeFromLeft (10);
  161. auto toggleBounds = bounds.removeFromLeft (height);
  162. drawToggle (g, toggleBounds, rowIsSelected);
  163. auto iconBounds = bounds.removeFromLeft (height).reduced (5);
  164. g.drawImageWithin (platform->icon, iconBounds.getX(), iconBounds.getY(), iconBounds.getWidth(),
  165. iconBounds.getHeight(), RectanglePlacement::fillDestination);
  166. bounds.removeFromLeft (10);
  167. g.setColour (findColour (widgetTextColourId));
  168. g.drawFittedText (platform->name, bounds, Justification::centredLeft, 1);
  169. }
  170. }
  171. void selectedRowsChanged (int) override
  172. {
  173. selectDefaultExporterIfNoneSelected();
  174. }
  175. private:
  176. struct PlatformType
  177. {
  178. PlatformType (const Image& platformIcon, const String& platformName)
  179. : icon (platformIcon), name (platformName)
  180. {
  181. }
  182. Image icon;
  183. String name;
  184. };
  185. void drawToggle (Graphics& g, Rectangle<int> bounds, bool isToggled)
  186. {
  187. auto sideLength = jmin (bounds.getWidth(), bounds.getHeight());
  188. bounds = bounds.withSizeKeepingCentre (sideLength, sideLength).reduced (4);
  189. g.setColour (findColour (ToggleButton::tickDisabledColourId));
  190. g.drawRoundedRectangle (bounds.toFloat(), 2.0f, 1.0f);
  191. if (isToggled)
  192. {
  193. g.setColour (findColour (ToggleButton::tickColourId));
  194. const auto tick = getTickShape (0.75f);
  195. g.fillPath (tick, tick.getTransformToScaleToFit (bounds.reduced (4, 5).toFloat(), false));
  196. }
  197. }
  198. Path getTickShape (float height)
  199. {
  200. 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,
  201. 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 };
  202. Path path;
  203. path.loadPathFromData (pathData, sizeof (pathData));
  204. path.scaleToFit (0, 0, height * 2.0f, height, true);
  205. return path;
  206. }
  207. ListBox listBox;
  208. OwnedArray<PlatformType> platforms;
  209. OwnedArray<ToggleButton> toggles;
  210. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PlatformTargetsComp)
  211. };
  212. //==============================================================================
  213. /**
  214. The Component for project creation.
  215. Features a file browser to select project destination and
  216. a list box of platform targets to generate.
  217. */
  218. class WizardComp : public Component,
  219. private ButtonListener,
  220. private ComboBoxListener,
  221. private TextEditorListener,
  222. private FileBrowserListener
  223. {
  224. public:
  225. WizardComp()
  226. : platformTargets(),
  227. projectName (TRANS("Project name")),
  228. nameLabel (String(), TRANS("Project Name") + ":"),
  229. typeLabel (String(), TRANS("Project Type") + ":"),
  230. fileBrowser (FileBrowserComponent::saveMode
  231. | FileBrowserComponent::canSelectDirectories
  232. | FileBrowserComponent::doNotClearFileNameOnRootChange,
  233. NewProjectWizardClasses::getLastWizardFolder(), nullptr, nullptr),
  234. fileOutline (String(), TRANS("Project Folder") + ":"),
  235. targetsOutline (String(), TRANS("Target Platforms") + ":"),
  236. createButton (TRANS("Create") + "..."),
  237. cancelButton (TRANS("Cancel")),
  238. modulesPathBox (findDefaultModulesFolder())
  239. {
  240. setOpaque (false);
  241. addChildAndSetID (&projectName, "projectName");
  242. projectName.setText ("NewProject");
  243. projectName.setBounds ("120, 34, parent.width / 2 - 10, top + 22");
  244. nameLabel.attachToComponent (&projectName, true);
  245. projectName.addListener (this);
  246. addChildAndSetID (&projectType, "projectType");
  247. projectType.addItemList (getWizardNames(), 1);
  248. projectType.setSelectedId (1, dontSendNotification);
  249. projectType.setBounds ("120, projectName.bottom + 4, projectName.right, top + 22");
  250. typeLabel.attachToComponent (&projectType, true);
  251. projectType.addListener (this);
  252. addChildAndSetID (&fileOutline, "fileOutline");
  253. fileOutline.setColour (GroupComponent::outlineColourId, Colours::black.withAlpha (0.2f));
  254. fileOutline.setTextLabelPosition (Justification::centred);
  255. fileOutline.setBounds ("30, projectType.bottom + 20, projectType.right, parent.height - 30");
  256. addChildAndSetID (&targetsOutline, "targetsOutline");
  257. targetsOutline.setColour (GroupComponent::outlineColourId, Colours::black.withAlpha (0.2f));
  258. targetsOutline.setTextLabelPosition (Justification::centred);
  259. targetsOutline.setBounds ("fileOutline.right + 20, projectType.bottom + 20, parent.width - 30, parent.height - 70");
  260. addChildAndSetID (&platformTargets, "platformTargets");
  261. platformTargets.setBounds ("targetsOutline.left + 15, projectType.bottom + 45, parent.width - 40, parent.height - 90");
  262. addChildAndSetID (&fileBrowser, "fileBrowser");
  263. fileBrowser.setBounds ("fileOutline.left + 10, fileOutline.top + 20, fileOutline.right - 10, fileOutline.bottom - 32");
  264. fileBrowser.setFilenameBoxLabel ("Folder:");
  265. fileBrowser.setFileName (File::createLegalFileName (projectName.getText()));
  266. fileBrowser.addListener (this);
  267. addChildAndSetID (&createButton, "createButton");
  268. createButton.setBounds ("right - 130, bottom - 34, parent.width - 30, parent.height - 30");
  269. createButton.addListener (this);
  270. addChildAndSetID (&cancelButton, "cancelButton");
  271. cancelButton.addShortcut (KeyPress (KeyPress::escapeKey));
  272. cancelButton.setBounds ("right - 130, createButton.top, createButton.left - 10, createButton.bottom");
  273. cancelButton.addListener (this);
  274. addChildAndSetID (&modulesPathBox, "modulesPathBox");
  275. modulesPathBox.setBounds ("targetsOutline.left, targetsOutline.top - 45, targetsOutline.right, targetsOutline.top - 20");
  276. updateCustomItems();
  277. updateCreateButton();
  278. lookAndFeelChanged();
  279. }
  280. void paint (Graphics& g) override
  281. {
  282. g.fillAll (findColour (backgroundColourId));
  283. }
  284. void buttonClicked (Button* b) override
  285. {
  286. if (b == &createButton)
  287. {
  288. createProject();
  289. }
  290. else if (b == &cancelButton)
  291. {
  292. returnToTemplatesPage();
  293. }
  294. }
  295. void returnToTemplatesPage()
  296. {
  297. if (SlidingPanelComponent* parent = findParentComponentOfClass<SlidingPanelComponent>())
  298. {
  299. if (parent->getNumTabs() > 0)
  300. parent->goToTab (parent->getCurrentTabIndex() - 1);
  301. }
  302. else
  303. {
  304. jassertfalse;
  305. }
  306. }
  307. void createProject()
  308. {
  309. MainWindow* mw = Component::findParentComponentOfClass<MainWindow>();
  310. jassert (mw != nullptr);
  311. ScopedPointer<NewProjectWizardClasses::NewProjectWizard> wizard (createWizard());
  312. if (wizard != nullptr)
  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. ScopedPointer<Project> project (wizard->runWizard (*this, projectName.getText(),
  327. fileBrowser.getSelectedFile (0)));
  328. if (project != nullptr)
  329. mw->setProject (project.release());
  330. }
  331. }
  332. void updateCustomItems()
  333. {
  334. customItems.clear();
  335. ScopedPointer<NewProjectWizardClasses::NewProjectWizard> wizard (createWizard());
  336. if (wizard != nullptr)
  337. wizard->addSetupItems (*this, customItems);
  338. }
  339. void comboBoxChanged (ComboBox*) override
  340. {
  341. updateCustomItems();
  342. }
  343. void textEditorTextChanged (TextEditor&) override
  344. {
  345. updateCreateButton();
  346. fileBrowser.setFileName (File::createLegalFileName (projectName.getText()));
  347. }
  348. void selectionChanged() override {}
  349. void fileClicked (const File&, const MouseEvent&) override {}
  350. void fileDoubleClicked (const File&) override {}
  351. void browserRootChanged (const File&) override
  352. {
  353. fileBrowser.setFileName (File::createLegalFileName (projectName.getText()));
  354. }
  355. ComboBox projectType;
  356. PlatformTargetsComp platformTargets;
  357. private:
  358. TextEditor projectName;
  359. Label nameLabel, typeLabel;
  360. FileBrowserComponent fileBrowser;
  361. GroupComponent fileOutline;
  362. GroupComponent targetsOutline;
  363. TextButton createButton, cancelButton;
  364. OwnedArray<Component> customItems;
  365. ModulesFolderPathBox modulesPathBox;
  366. NewProjectWizardClasses::NewProjectWizard* createWizard()
  367. {
  368. return createWizardType (projectType.getSelectedItemIndex());
  369. }
  370. void updateCreateButton()
  371. {
  372. createButton.setEnabled (projectName.getText().trim().isNotEmpty());
  373. }
  374. void lookAndFeelChanged() override
  375. {
  376. projectName.setColour (TextEditor::backgroundColourId, findColour (backgroundColourId));
  377. projectName.setColour (TextEditor::textColourId, findColour (defaultTextColourId));
  378. projectName.setColour (TextEditor::outlineColourId, findColour (defaultTextColourId));
  379. projectName.applyFontToAllText (projectName.getFont());
  380. fileBrowser.resized();
  381. }
  382. };