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. struct IconButton : public Button
  20. {
  21. IconButton (String name, const Path* p)
  22. : Button (name),
  23. icon (p, Colours::transparentBlack)
  24. {
  25. lookAndFeelChanged();
  26. setTooltip (name);
  27. }
  28. void paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown) override
  29. {
  30. auto alpha = 1.0f;
  31. if (! isEnabled())
  32. {
  33. isMouseOverButton = false;
  34. isButtonDown = false;
  35. alpha = 0.2f;
  36. }
  37. auto backgroundColour = isIDEButton ? Colours::white
  38. : isUserButton ? findColour (userButtonBackgroundColourId)
  39. : findColour (defaultButtonBackgroundColourId);
  40. backgroundColour = isButtonDown ? backgroundColour.darker (0.5f)
  41. : isMouseOverButton ? backgroundColour.darker (0.2f)
  42. : backgroundColour;
  43. auto bounds = getLocalBounds().toFloat();
  44. if (isButtonDown)
  45. bounds.reduce (2, 2);
  46. Path ellipse;
  47. ellipse.addEllipse (bounds);
  48. g.reduceClipRegion(ellipse);
  49. g.setColour (backgroundColour.withAlpha (alpha));
  50. g.fillAll();
  51. if (iconImage != Image())
  52. {
  53. if (isIDEButton)
  54. bounds.reduce (7, 7);
  55. g.setOpacity (alpha);
  56. g.drawImage (iconImage, bounds, RectanglePlacement::fillDestination, false);
  57. }
  58. else
  59. {
  60. icon.withColour (findColour (defaultIconColourId).withAlpha (alpha)).draw (g, bounds.reduced (2, 2), false);
  61. }
  62. }
  63. Icon icon;
  64. Image iconImage;
  65. bool isIDEButton = false;
  66. bool isUserButton = false;
  67. };
  68. //==============================================================================
  69. class UserSettingsPopup : public Component
  70. #if ! JUCER_ENABLE_GPL_MODE
  71. , private Button::Listener,
  72. private LicenseController::StateChangedCallback
  73. #endif
  74. {
  75. public:
  76. UserSettingsPopup (bool isShownInsideWebview)
  77. #if ! JUCER_ENABLE_GPL_MODE
  78. : isInsideWebview (isShownInsideWebview)
  79. #endif
  80. {
  81. #if JUCER_ENABLE_GPL_MODE
  82. ignoreUnused (isShownInsideWebview);
  83. #endif
  84. auto standardFont = Font (12.0f);
  85. addAndMakeVisible (loggedInUsernameLabel = new Label ("Username Label"));
  86. loggedInUsernameLabel->setFont (standardFont);
  87. loggedInUsernameLabel->setJustificationType (Justification::centred);
  88. loggedInUsernameLabel->setMinimumHorizontalScale (0.75f);
  89. #if JUCER_ENABLE_GPL_MODE
  90. loggedInUsernameLabel->setText ("GPL Mode: Re-compile with JUCER_ENABLE_GPL_MODE=0 to enable login!",
  91. NotificationType::dontSendNotification);
  92. #else
  93. addAndMakeVisible (licenseTypeLabel = new Label ("License Type Label"));
  94. licenseTypeLabel->setFont (standardFont);
  95. licenseTypeLabel->setJustificationType (Justification::centred);
  96. licenseTypeLabel->setMinimumHorizontalScale (1.0f);
  97. addAndMakeVisible (logoutButton = new TextButton (isInsideWebview ? "Select different account..." : "Logout"));
  98. logoutButton->addListener (this);
  99. logoutButton->setColour (TextButton::buttonColourId, findColour (secondaryButtonBackgroundColourId));
  100. if (! isInsideWebview)
  101. {
  102. addAndMakeVisible (switchLicenseButton = new TextButton ("Switch License"));
  103. switchLicenseButton->addListener (this);
  104. }
  105. if (LicenseController* controller = ProjucerApplication::getApp().licenseController)
  106. licenseStateChanged (controller->getState());
  107. #endif
  108. }
  109. void paint (Graphics& g) override
  110. {
  111. g.fillAll (findColour (secondaryBackgroundColourId));
  112. }
  113. void resized() override
  114. {
  115. auto bounds = getLocalBounds().reduced (10, 20);
  116. #if JUCER_ENABLE_GPL_MODE
  117. loggedInUsernameLabel->setBounds (bounds);
  118. #else
  119. loggedInUsernameLabel->setBounds (bounds.removeFromTop (25));
  120. if (hasLicenseType)
  121. {
  122. bounds.removeFromTop (10);
  123. licenseTypeLabel->setBounds (bounds.removeFromTop (25));
  124. }
  125. bounds.removeFromBottom (5);
  126. auto buttonArea = bounds.removeFromBottom (30);
  127. if (! isInsideWebview)
  128. switchLicenseButton->setBounds (buttonArea.removeFromRight (buttonArea.getWidth() / 2).reduced (2));
  129. logoutButton->setBounds (buttonArea.reduced (2));
  130. #endif
  131. }
  132. private:
  133. //==============================================================================
  134. #if ! JUCER_ENABLE_GPL_MODE
  135. void buttonClicked (Button* b) override
  136. {
  137. if (b == logoutButton)
  138. {
  139. dismissCalloutBox();
  140. ProjucerApplication::getApp().doLogout();
  141. }
  142. else if (b == switchLicenseButton)
  143. {
  144. dismissCalloutBox();
  145. if (LicenseController* controller = ProjucerApplication::getApp().licenseController)
  146. controller->chooseNewLicense();
  147. }
  148. }
  149. void licenseStateChanged (const LicenseState& state) override
  150. {
  151. hasLicenseType = (state.type != LicenseState::Type::noLicenseChosenYet);
  152. licenseTypeLabel->setVisible (hasLicenseType);
  153. loggedInUsernameLabel->setText (state.username, NotificationType::dontSendNotification);
  154. licenseTypeLabel->setText (LicenseState::licenseTypeToString (state.type), NotificationType::dontSendNotification);
  155. }
  156. void dismissCalloutBox()
  157. {
  158. if (auto* parent = findParentComponentOfClass<CallOutBox>())
  159. parent->dismiss();
  160. }
  161. void lookAndFeelChanged() override
  162. {
  163. if (logoutButton != nullptr)
  164. logoutButton->setColour (TextButton::buttonColourId, findColour (secondaryButtonBackgroundColourId));
  165. }
  166. #endif
  167. //==============================================================================
  168. ScopedPointer<Label> loggedInUsernameLabel;
  169. #if ! JUCER_ENABLE_GPL_MODE
  170. ScopedPointer<Label> licenseTypeLabel;
  171. ScopedPointer<TextButton> logoutButton, switchLicenseButton;
  172. bool hasLicenseType = false;
  173. bool isInsideWebview;
  174. #endif
  175. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UserSettingsPopup)
  176. };
  177. //==============================================================================
  178. class HeaderComponent : public Component,
  179. private Button::Listener,
  180. private ComboBox::Listener,
  181. private ValueTree::Listener,
  182. private ChangeListener
  183. {
  184. public:
  185. HeaderComponent()
  186. : configLabel ("Config Label", "Selected exporter")
  187. {
  188. addAndMakeVisible (configLabel);
  189. addAndMakeVisible (exporterBox);
  190. exporterBox.addListener (this);
  191. addAndMakeVisible (juceIcon = new ImageComponent ("icon"));
  192. juceIcon->setImage (ImageCache::getFromMemory (BinaryData::juce_icon_png, BinaryData::juce_icon_pngSize),
  193. RectanglePlacement::centred);
  194. projectNameLabel.setText (String(), dontSendNotification);
  195. addAndMakeVisible (projectNameLabel);
  196. initialiseButtons();
  197. }
  198. ~HeaderComponent()
  199. {
  200. if (userSettingsWindow != nullptr)
  201. userSettingsWindow->dismiss();
  202. }
  203. void resized() override
  204. {
  205. auto bounds = getLocalBounds();
  206. configLabel.setFont (Font (bounds.getHeight() / 3.0f));
  207. //======================================================================
  208. auto projectHeaderBounds = bounds.removeFromLeft (tabsWidth);
  209. juceIcon->setBounds (projectHeaderBounds.removeFromLeft (projectHeaderBounds.getHeight()).reduced (5, 5));
  210. projectSettingsButton->setBounds (projectHeaderBounds.removeFromRight (projectHeaderBounds.getHeight()).reduced (2, 2));
  211. projectNameLabel.setBounds (projectHeaderBounds);
  212. //======================================================================
  213. bounds.removeFromLeft (33);
  214. continuousRebuildButton->setBounds (bounds.removeFromLeft (bounds.getHeight()).reduced (2, 2));
  215. bounds.removeFromLeft (5);
  216. buildNowButton->setBounds (bounds.removeFromLeft (bounds.getHeight()).reduced (2, 2));
  217. bounds.removeFromRight (5);
  218. userSettingsButton->setBounds (bounds.removeFromRight (bounds.getHeight()).reduced (2, 2));
  219. auto exporterWidth = jmax (250, bounds.getWidth() / 2);
  220. auto spacing = bounds.getWidth() - exporterWidth;
  221. auto leftSpacing = jmax (20, spacing / 3);
  222. auto rightSpacing = jmax (40, 2 * (spacing / 3));
  223. bounds.removeFromLeft (leftSpacing);
  224. bounds.removeFromRight (rightSpacing);
  225. saveAndOpenInIDEButton->setBounds (bounds.removeFromRight (bounds.getHeight()).reduced (2, 2));
  226. bounds.removeFromRight (5);
  227. exporterSettingsButton->setBounds (bounds.removeFromRight (bounds.getHeight()).reduced (2, 2));
  228. bounds.removeFromRight (10);
  229. exporterBox.setBounds (bounds.removeFromBottom (roundToInt (bounds.getHeight() / 1.8f)));
  230. configLabel.setBounds (bounds);
  231. }
  232. void paint (Graphics& g) override
  233. {
  234. g.fillAll (findColour (backgroundColourId));
  235. }
  236. void setCurrentProject (Project* p)
  237. {
  238. project = p;
  239. exportersTree = project->getExporters();
  240. exportersTree.addListener (this);
  241. updateExporters();
  242. project->addChangeListener (this);
  243. updateName();
  244. continuousRebuildButton->setEnabled (true);
  245. updateContinuousRebuildButtonIcon();
  246. if (auto* pcc = findParentComponentOfClass<ProjectContentComponent>())
  247. buildNowButton->setEnabled (! pcc->isContinuousRebuildEnabled() || ! pcc->isBuildEnabled());
  248. }
  249. void updateExporters()
  250. {
  251. auto selectedName = getSelectedExporterName();
  252. exporterBox.clear();
  253. int i = 0;
  254. for (Project::ExporterIterator exporter (*project); exporter.next(); ++i)
  255. {
  256. exporterBox.addItem (exporter->getName(), i + 1);
  257. if (selectedName == exporter->getName())
  258. exporterBox.setSelectedId (i + 1);
  259. }
  260. if (exporterBox.getSelectedItemIndex() == -1)
  261. exporterBox.setSelectedItemIndex (0);
  262. updateExporterButton();
  263. }
  264. String getSelectedExporterName()
  265. {
  266. return exporterBox.getItemText (exporterBox.getSelectedItemIndex());
  267. }
  268. bool canCurrentExporterLaunchProject()
  269. {
  270. for (Project::ExporterIterator exporter (*project); exporter.next();)
  271. if (exporter->getName() == getSelectedExporterName() && exporter->canLaunchProject())
  272. return true;
  273. return false;
  274. }
  275. int getUserButtonWidth() { return userSettingsButton->getWidth(); }
  276. void sidebarTabsWidthChanged (int newWidth)
  277. {
  278. tabsWidth = newWidth;
  279. resized();
  280. }
  281. void showUserSettings()
  282. {
  283. #if JUCER_ENABLE_GPL_MODE
  284. const int settingsPopupHeight = 75;
  285. #else
  286. const int settingsPopupHeight = 150;
  287. #endif
  288. auto* content = new UserSettingsPopup (false);
  289. content->setSize (200, settingsPopupHeight);
  290. userSettingsWindow = &CallOutBox::launchAsynchronously (content, userSettingsButton->getScreenBounds(), nullptr);
  291. }
  292. void lookAndFeelChanged() override
  293. {
  294. if (userSettingsWindow != nullptr)
  295. userSettingsWindow->sendLookAndFeelChange();
  296. }
  297. private:
  298. Project* project = nullptr;
  299. ValueTree exportersTree;
  300. Label configLabel, projectNameLabel;
  301. ComboBox exporterBox;
  302. ScopedPointer<ImageComponent> juceIcon;
  303. ScopedPointer<IconButton> projectSettingsButton, continuousRebuildButton, buildNowButton,
  304. exporterSettingsButton, saveAndOpenInIDEButton, userSettingsButton;
  305. SafePointer<CallOutBox> userSettingsWindow;
  306. int tabsWidth = 200;
  307. //==========================================================================
  308. void buttonClicked (Button* b) override
  309. {
  310. auto* pcc = findParentComponentOfClass<ProjectContentComponent>();
  311. if (b == projectSettingsButton)
  312. {
  313. pcc->showProjectSettings();
  314. }
  315. else if (b == continuousRebuildButton)
  316. {
  317. if (! pcc->isBuildEnabled())
  318. pcc->setBuildEnabled (true);
  319. auto newState = ! pcc->isContinuousRebuildEnabled();
  320. pcc->setContinuousRebuildEnabled (newState);
  321. updateContinuousRebuildButtonIcon();
  322. buildNowButton->setEnabled (! pcc->isContinuousRebuildEnabled() || ! pcc->isBuildEnabled());
  323. }
  324. else if (b == buildNowButton)
  325. {
  326. if (! pcc->isBuildEnabled())
  327. pcc->setBuildEnabled (true);
  328. pcc->rebuildNow();
  329. }
  330. else if (b == exporterSettingsButton)
  331. {
  332. pcc->showExporterSettings (getSelectedExporterName());
  333. }
  334. else if (b == saveAndOpenInIDEButton)
  335. {
  336. pcc->openInSelectedIDE (true);
  337. }
  338. else if (b == userSettingsButton)
  339. {
  340. showUserSettings();
  341. }
  342. }
  343. void comboBoxChanged (ComboBox* c) override
  344. {
  345. if (c == &exporterBox)
  346. updateExporterButton();
  347. }
  348. void changeListenerCallback (ChangeBroadcaster* source) override
  349. {
  350. if (source == project)
  351. updateName();
  352. }
  353. void valueTreePropertyChanged (ValueTree&, const Identifier&) override {}
  354. void valueTreeParentChanged (ValueTree&) override {}
  355. void valueTreeChildAdded (ValueTree& parentTree, ValueTree&) override { updateIfNeeded (parentTree); }
  356. void valueTreeChildRemoved (ValueTree& parentTree, ValueTree&, int) override { updateIfNeeded (parentTree); }
  357. void valueTreeChildOrderChanged (ValueTree& parentTree, int, int) override { updateIfNeeded (parentTree); }
  358. void initialiseButtons()
  359. {
  360. auto& icons = getIcons();
  361. addAndMakeVisible (projectSettingsButton = new IconButton ("Project Settings", &icons.settings));
  362. projectSettingsButton->addListener (this);
  363. addAndMakeVisible (continuousRebuildButton = new IconButton ("Continuous Rebuild", &icons.continuousBuildStart));
  364. continuousRebuildButton->addListener (this);
  365. continuousRebuildButton->setEnabled (false);
  366. addAndMakeVisible (buildNowButton = new IconButton ("Build Now", &icons.buildNow));
  367. buildNowButton->addListener (this);
  368. buildNowButton->setEnabled (false);
  369. addAndMakeVisible (exporterSettingsButton = new IconButton ("Exporter Settings", &icons.edit));
  370. exporterSettingsButton->addListener (this);
  371. addAndMakeVisible (saveAndOpenInIDEButton = new IconButton ("Save and Open in IDE", nullptr));
  372. saveAndOpenInIDEButton->addListener (this);
  373. saveAndOpenInIDEButton->isIDEButton = true;
  374. addAndMakeVisible (userSettingsButton = new IconButton ("User Settings", &icons.user));
  375. userSettingsButton->addListener (this);
  376. userSettingsButton->isUserButton = true;
  377. updateExporterButton();
  378. updateUserAvatar();
  379. }
  380. void updateIfNeeded (ValueTree tree)
  381. {
  382. if (tree == exportersTree)
  383. updateExporters();
  384. }
  385. void updateName()
  386. {
  387. projectNameLabel.setText (project->getDocumentTitle(), dontSendNotification);
  388. }
  389. void updateExporterButton()
  390. {
  391. auto currentExporterName = getSelectedExporterName();
  392. for (auto info : ProjectExporter::getExporterTypes())
  393. {
  394. if (info.name == currentExporterName)
  395. {
  396. saveAndOpenInIDEButton->iconImage = info.getIcon();
  397. saveAndOpenInIDEButton->repaint();
  398. saveAndOpenInIDEButton->setEnabled (canCurrentExporterLaunchProject());
  399. }
  400. }
  401. }
  402. void updateUserAvatar()
  403. {
  404. if (LicenseController* controller = ProjucerApplication::getApp().licenseController)
  405. {
  406. auto& state = controller->getState();
  407. userSettingsButton->iconImage = state.avatar;
  408. userSettingsButton->repaint();
  409. }
  410. }
  411. void updateContinuousRebuildButtonIcon()
  412. {
  413. if (auto* pcc = findParentComponentOfClass<ProjectContentComponent>())
  414. {
  415. continuousRebuildButton->setEnabled (pcc->isBuildEnabled());
  416. continuousRebuildButton->icon = Icon (pcc->isContinuousRebuildEnabled() ? &getIcons().continuousBuildStop
  417. : &getIcons().continuousBuildStart, Colours::transparentBlack);
  418. }
  419. }
  420. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HeaderComponent)
  421. };