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.

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