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