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.

478 lines
16KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2015 - ROLI Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. #pragma once
  18. class ModulesFolderPathBox : public Component,
  19. private ButtonListener,
  20. private ComboBoxListener
  21. {
  22. public:
  23. ModulesFolderPathBox (File initialFileOrDirectory)
  24. : currentPathBox ("currentPathBox"),
  25. openFolderButton (TRANS("...")),
  26. modulesLabel (String(), TRANS("Modules Folder") + ":")
  27. {
  28. if (initialFileOrDirectory == File())
  29. initialFileOrDirectory = findDefaultModulesFolder();
  30. setModulesFolder (initialFileOrDirectory);
  31. addAndMakeVisible (currentPathBox);
  32. currentPathBox.setEditableText (true);
  33. currentPathBox.addListener (this);
  34. addAndMakeVisible (openFolderButton);
  35. openFolderButton.addListener (this);
  36. openFolderButton.setTooltip (TRANS ("Select JUCE modules folder"));
  37. addAndMakeVisible (modulesLabel);
  38. modulesLabel.attachToComponent (&currentPathBox, true);
  39. }
  40. void resized() override
  41. {
  42. auto r = getLocalBounds();
  43. openFolderButton.setBounds (r.removeFromRight (30));
  44. modulesLabel.setBounds (r.removeFromLeft (110));
  45. currentPathBox.setBounds (r);
  46. }
  47. static bool selectJuceFolder (File& result)
  48. {
  49. for (;;)
  50. {
  51. FileChooser fc ("Select your JUCE modules folder...",
  52. findDefaultModulesFolder(),
  53. "*");
  54. if (! fc.browseForDirectory())
  55. return false;
  56. if (isJuceModulesFolder (fc.getResult()))
  57. {
  58. result = fc.getResult();
  59. return true;
  60. }
  61. AlertWindow::showMessageBox (AlertWindow::WarningIcon,
  62. "Not a valid JUCE modules folder!",
  63. "Please select the folder containing your juce_* modules!\n\n"
  64. "This is required so that the new project can be given some essential core modules.");
  65. }
  66. }
  67. void selectJuceFolder()
  68. {
  69. File result;
  70. if (selectJuceFolder (result))
  71. setModulesFolder (result);
  72. }
  73. void setModulesFolder (const File& newFolder)
  74. {
  75. if (modulesFolder != newFolder)
  76. {
  77. modulesFolder = newFolder;
  78. currentPathBox.setText (modulesFolder.getFullPathName(), dontSendNotification);
  79. }
  80. }
  81. void buttonClicked (Button*) override
  82. {
  83. selectJuceFolder();
  84. }
  85. void comboBoxChanged (ComboBox*) override
  86. {
  87. setModulesFolder (File::getCurrentWorkingDirectory().getChildFile (currentPathBox.getText()));
  88. }
  89. File modulesFolder;
  90. private:
  91. ComboBox currentPathBox;
  92. TextButton openFolderButton;
  93. Label modulesLabel;
  94. };
  95. /** The target platforms chooser for the chosen template. */
  96. class PlatformTargetsComp : public Component,
  97. private ListBoxModel
  98. {
  99. public:
  100. PlatformTargetsComp()
  101. {
  102. setOpaque (false);
  103. const Array<ProjectExporter::ExporterTypeInfo> types (ProjectExporter::getExporterTypes());
  104. for (int i = 0; i < types.size(); ++i)
  105. {
  106. const ProjectExporter::ExporterTypeInfo& type = types.getReference (i);
  107. platforms.add (new PlatformType (type.getIcon(), type.name));
  108. addAndMakeVisible (toggles.add (new ToggleButton (String())));
  109. }
  110. listBox.setRowHeight (30);
  111. listBox.setModel (this);
  112. listBox.setOpaque (false);
  113. listBox.setMultipleSelectionEnabled (true);
  114. listBox.setClickingTogglesRowSelection (true);
  115. listBox.setColour (ListBox::backgroundColourId, Colours::transparentBlack);
  116. addAndMakeVisible (listBox);
  117. selectDefaultExporterIfNoneSelected();
  118. }
  119. StringArray getSelectedPlatforms() const
  120. {
  121. StringArray list;
  122. for (int i = 0; i < platforms.size(); ++i)
  123. if (listBox.isRowSelected (i))
  124. list.add (platforms.getUnchecked(i)->name);
  125. return list;
  126. }
  127. void selectDefaultExporterIfNoneSelected()
  128. {
  129. if (listBox.getNumSelectedRows() == 0)
  130. {
  131. for (int i = platforms.size(); --i >= 0;)
  132. {
  133. if (platforms.getUnchecked(i)->name == ProjectExporter::getCurrentPlatformExporterName())
  134. {
  135. listBox.selectRow (i);
  136. break;
  137. }
  138. }
  139. }
  140. }
  141. void resized() override
  142. {
  143. listBox.setBounds (getLocalBounds());
  144. }
  145. int getNumRows() override
  146. {
  147. return platforms.size();
  148. }
  149. void paintListBoxItem (int rowNumber, Graphics& g, int width, int height, bool rowIsSelected) override
  150. {
  151. ignoreUnused (width);
  152. if (PlatformType* platform = platforms[rowNumber])
  153. {
  154. auto bounds = getLocalBounds().withHeight (height).withTrimmedBottom (1);
  155. g.setColour (findColour (rowNumber % 2 == 0 ? widgetBackgroundColourId
  156. : secondaryWidgetBackgroundColourId));
  157. g.fillRect (bounds);
  158. bounds.removeFromLeft (10);
  159. auto toggleBounds = bounds.removeFromLeft (height);
  160. drawToggle (g, toggleBounds, rowIsSelected);
  161. auto iconBounds = bounds.removeFromLeft (height).reduced (5);
  162. g.drawImageWithin (platform->icon, iconBounds.getX(), iconBounds.getY(), iconBounds.getWidth(),
  163. iconBounds.getHeight(), RectanglePlacement::fillDestination);
  164. bounds.removeFromLeft (10);
  165. g.setColour (findColour (widgetTextColourId));
  166. g.drawFittedText (platform->name, bounds, Justification::centredLeft, 1);
  167. }
  168. }
  169. void selectedRowsChanged (int) override
  170. {
  171. selectDefaultExporterIfNoneSelected();
  172. }
  173. private:
  174. struct PlatformType
  175. {
  176. PlatformType (const Image& platformIcon, const String& platformName)
  177. : icon (platformIcon), name (platformName)
  178. {
  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 ButtonListener,
  218. private ComboBoxListener,
  219. private TextEditorListener,
  220. private FileBrowserListener
  221. {
  222. public:
  223. WizardComp()
  224. : platformTargets(),
  225. projectName (TRANS("Project name")),
  226. nameLabel (String(), TRANS("Project Name") + ":"),
  227. typeLabel (String(), TRANS("Project Type") + ":"),
  228. fileBrowser (FileBrowserComponent::saveMode
  229. | FileBrowserComponent::canSelectDirectories
  230. | FileBrowserComponent::doNotClearFileNameOnRootChange,
  231. NewProjectWizardClasses::getLastWizardFolder(), nullptr, nullptr),
  232. fileOutline (String(), TRANS("Project Folder") + ":"),
  233. targetsOutline (String(), TRANS("Target Platforms") + ":"),
  234. createButton (TRANS("Create") + "..."),
  235. cancelButton (TRANS("Cancel")),
  236. modulesPathBox (findDefaultModulesFolder())
  237. {
  238. setOpaque (false);
  239. addChildAndSetID (&projectName, "projectName");
  240. projectName.setText ("NewProject");
  241. projectName.setBounds ("120, 34, parent.width / 2 - 10, top + 22");
  242. nameLabel.attachToComponent (&projectName, true);
  243. projectName.addListener (this);
  244. addChildAndSetID (&projectType, "projectType");
  245. projectType.addItemList (getWizardNames(), 1);
  246. projectType.setSelectedId (1, dontSendNotification);
  247. projectType.setBounds ("120, projectName.bottom + 4, projectName.right, top + 22");
  248. typeLabel.attachToComponent (&projectType, true);
  249. projectType.addListener (this);
  250. addChildAndSetID (&fileOutline, "fileOutline");
  251. fileOutline.setColour (GroupComponent::outlineColourId, Colours::black.withAlpha (0.2f));
  252. fileOutline.setTextLabelPosition (Justification::centred);
  253. fileOutline.setBounds ("30, projectType.bottom + 20, projectType.right, parent.height - 30");
  254. addChildAndSetID (&targetsOutline, "targetsOutline");
  255. targetsOutline.setColour (GroupComponent::outlineColourId, Colours::black.withAlpha (0.2f));
  256. targetsOutline.setTextLabelPosition (Justification::centred);
  257. targetsOutline.setBounds ("fileOutline.right + 20, projectType.bottom + 20, parent.width - 30, parent.height - 70");
  258. addChildAndSetID (&platformTargets, "platformTargets");
  259. platformTargets.setBounds ("targetsOutline.left + 15, projectType.bottom + 45, parent.width - 40, parent.height - 90");
  260. addChildAndSetID (&fileBrowser, "fileBrowser");
  261. fileBrowser.setBounds ("fileOutline.left + 10, fileOutline.top + 20, fileOutline.right - 10, fileOutline.bottom - 32");
  262. fileBrowser.setFilenameBoxLabel ("Folder:");
  263. fileBrowser.setFileName (File::createLegalFileName (projectName.getText()));
  264. fileBrowser.addListener (this);
  265. addChildAndSetID (&createButton, "createButton");
  266. createButton.setBounds ("right - 130, bottom - 34, parent.width - 30, parent.height - 30");
  267. createButton.addListener (this);
  268. addChildAndSetID (&cancelButton, "cancelButton");
  269. cancelButton.addShortcut (KeyPress (KeyPress::escapeKey));
  270. cancelButton.setBounds ("right - 130, createButton.top, createButton.left - 10, createButton.bottom");
  271. cancelButton.addListener (this);
  272. addChildAndSetID (&modulesPathBox, "modulesPathBox");
  273. modulesPathBox.setBounds ("targetsOutline.left, targetsOutline.top - 45, targetsOutline.right, targetsOutline.top - 20");
  274. updateCustomItems();
  275. updateCreateButton();
  276. lookAndFeelChanged();
  277. }
  278. void paint (Graphics& g) override
  279. {
  280. g.fillAll (findColour (backgroundColourId));
  281. }
  282. void buttonClicked (Button* b) override
  283. {
  284. if (b == &createButton)
  285. {
  286. createProject();
  287. }
  288. else if (b == &cancelButton)
  289. {
  290. returnToTemplatesPage();
  291. }
  292. }
  293. void returnToTemplatesPage()
  294. {
  295. if (SlidingPanelComponent* parent = findParentComponentOfClass<SlidingPanelComponent>())
  296. {
  297. if (parent->getNumTabs() > 0)
  298. parent->goToTab (parent->getCurrentTabIndex() - 1);
  299. }
  300. else
  301. {
  302. jassertfalse;
  303. }
  304. }
  305. void createProject()
  306. {
  307. MainWindow* mw = Component::findParentComponentOfClass<MainWindow>();
  308. jassert (mw != nullptr);
  309. ScopedPointer<NewProjectWizardClasses::NewProjectWizard> wizard (createWizard());
  310. if (wizard != nullptr)
  311. {
  312. Result result (wizard->processResultsFromSetupItems (*this));
  313. if (result.failed())
  314. {
  315. AlertWindow::showMessageBox (AlertWindow::WarningIcon,
  316. TRANS("Create Project"),
  317. result.getErrorMessage());
  318. return;
  319. }
  320. wizard->modulesFolder = modulesPathBox.modulesFolder;
  321. if (! isJuceModulesFolder (wizard->modulesFolder))
  322. if (! wizard->selectJuceFolder())
  323. return;
  324. ScopedPointer<Project> project (wizard->runWizard (*this, projectName.getText(),
  325. fileBrowser.getSelectedFile (0)));
  326. if (project != nullptr)
  327. mw->setProject (project.release());
  328. }
  329. }
  330. void updateCustomItems()
  331. {
  332. customItems.clear();
  333. ScopedPointer<NewProjectWizardClasses::NewProjectWizard> wizard (createWizard());
  334. if (wizard != nullptr)
  335. wizard->addSetupItems (*this, customItems);
  336. }
  337. void comboBoxChanged (ComboBox*) override
  338. {
  339. updateCustomItems();
  340. }
  341. void textEditorTextChanged (TextEditor&) override
  342. {
  343. updateCreateButton();
  344. fileBrowser.setFileName (File::createLegalFileName (projectName.getText()));
  345. }
  346. void selectionChanged() override {}
  347. void fileClicked (const File&, const MouseEvent&) override {}
  348. void fileDoubleClicked (const File&) override {}
  349. void browserRootChanged (const File&) override
  350. {
  351. fileBrowser.setFileName (File::createLegalFileName (projectName.getText()));
  352. }
  353. ComboBox projectType;
  354. PlatformTargetsComp platformTargets;
  355. private:
  356. TextEditor projectName;
  357. Label nameLabel, typeLabel;
  358. FileBrowserComponent fileBrowser;
  359. GroupComponent fileOutline;
  360. GroupComponent targetsOutline;
  361. TextButton createButton, cancelButton;
  362. OwnedArray<Component> customItems;
  363. ModulesFolderPathBox modulesPathBox;
  364. NewProjectWizardClasses::NewProjectWizard* createWizard()
  365. {
  366. return createWizardType (projectType.getSelectedItemIndex());
  367. }
  368. void updateCreateButton()
  369. {
  370. createButton.setEnabled (projectName.getText().trim().isNotEmpty());
  371. }
  372. void lookAndFeelChanged() override
  373. {
  374. projectName.setColour (TextEditor::backgroundColourId, findColour (backgroundColourId));
  375. projectName.setColour (TextEditor::textColourId, findColour (defaultTextColourId));
  376. projectName.setColour (TextEditor::outlineColourId, findColour (defaultTextColourId));
  377. projectName.applyFontToAllText (projectName.getFont());
  378. fileBrowser.resized();
  379. }
  380. };