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.

536 lines
18KB

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