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.

506 lines
18KB

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