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.

511 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. if (auto* pcc = findParentComponentOfClass<ProjectContentComponent>())
  245. updateBuildButtons (pcc->isBuildEnabled(), pcc->isContinuousRebuildEnabled());
  246. }
  247. void updateExporters()
  248. {
  249. auto selectedName = getSelectedExporterName();
  250. exporterBox.clear();
  251. auto preferredExporterIndex = -1;
  252. int i = 0;
  253. for (Project::ExporterIterator exporter (*project); exporter.next(); ++i)
  254. {
  255. exporterBox.addItem (exporter->getName(), i + 1);
  256. if (selectedName == exporter->getName())
  257. exporterBox.setSelectedId (i + 1);
  258. if (exporter->canLaunchProject() && preferredExporterIndex == -1)
  259. preferredExporterIndex = i;
  260. }
  261. if (exporterBox.getSelectedItemIndex() == -1)
  262. exporterBox.setSelectedItemIndex (preferredExporterIndex != -1 ? preferredExporterIndex
  263. : 0);
  264. updateExporterButton();
  265. }
  266. String getSelectedExporterName()
  267. {
  268. return exporterBox.getItemText (exporterBox.getSelectedItemIndex());
  269. }
  270. bool canCurrentExporterLaunchProject()
  271. {
  272. for (Project::ExporterIterator exporter (*project); exporter.next();)
  273. if (exporter->getName() == getSelectedExporterName() && exporter->canLaunchProject())
  274. return true;
  275. return false;
  276. }
  277. int getUserButtonWidth() { return userSettingsButton->getWidth(); }
  278. void sidebarTabsWidthChanged (int newWidth)
  279. {
  280. tabsWidth = newWidth;
  281. resized();
  282. }
  283. void showUserSettings()
  284. {
  285. #if JUCER_ENABLE_GPL_MODE
  286. const int settingsPopupHeight = 75;
  287. #else
  288. const int settingsPopupHeight = 150;
  289. #endif
  290. auto* content = new UserSettingsPopup (false);
  291. content->setSize (200, settingsPopupHeight);
  292. userSettingsWindow = &CallOutBox::launchAsynchronously (content, userSettingsButton->getScreenBounds(), nullptr);
  293. }
  294. void updateBuildButtons (bool isBuildEnabled, bool isContinuousRebuildEnabled)
  295. {
  296. buildNowButton->setEnabled (isBuildEnabled && ! isContinuousRebuildEnabled);
  297. continuousRebuildButton->setEnabled (isBuildEnabled);
  298. continuousRebuildButton->icon = Icon (isContinuousRebuildEnabled ? &getIcons().continuousBuildStop : &getIcons().continuousBuildStart,
  299. Colours::transparentBlack);
  300. repaint();
  301. }
  302. void lookAndFeelChanged() override
  303. {
  304. if (userSettingsWindow != nullptr)
  305. userSettingsWindow->sendLookAndFeelChange();
  306. }
  307. private:
  308. Project* project = nullptr;
  309. ValueTree exportersTree;
  310. Label configLabel, projectNameLabel;
  311. ComboBox exporterBox;
  312. ScopedPointer<ImageComponent> juceIcon;
  313. ScopedPointer<IconButton> projectSettingsButton, continuousRebuildButton, buildNowButton,
  314. exporterSettingsButton, saveAndOpenInIDEButton, userSettingsButton;
  315. SafePointer<CallOutBox> userSettingsWindow;
  316. int tabsWidth = 200;
  317. //==========================================================================
  318. void buttonClicked (Button* b) override
  319. {
  320. auto* pcc = findParentComponentOfClass<ProjectContentComponent>();
  321. if (b == projectSettingsButton) pcc->showProjectSettings();
  322. else if (b == continuousRebuildButton) pcc->setContinuousRebuildEnabled (! pcc->isContinuousRebuildEnabled());
  323. else if (b == buildNowButton) pcc->rebuildNow();
  324. else if (b == exporterSettingsButton) pcc->showExporterSettings (getSelectedExporterName());
  325. else if (b == saveAndOpenInIDEButton) pcc->openInSelectedIDE (true);
  326. else if (b == userSettingsButton) showUserSettings();
  327. }
  328. void comboBoxChanged (ComboBox* c) override
  329. {
  330. if (c == &exporterBox)
  331. updateExporterButton();
  332. }
  333. void changeListenerCallback (ChangeBroadcaster* source) override
  334. {
  335. if (source == project)
  336. {
  337. updateName();
  338. updateExporters();
  339. }
  340. }
  341. void valueTreePropertyChanged (ValueTree&, const Identifier&) override {}
  342. void valueTreeParentChanged (ValueTree&) override {}
  343. void valueTreeChildAdded (ValueTree& parentTree, ValueTree&) override { updateIfNeeded (parentTree); }
  344. void valueTreeChildRemoved (ValueTree& parentTree, ValueTree&, int) override { updateIfNeeded (parentTree); }
  345. void valueTreeChildOrderChanged (ValueTree& parentTree, int, int) override { updateIfNeeded (parentTree); }
  346. void initialiseButtons()
  347. {
  348. auto& icons = getIcons();
  349. addAndMakeVisible (projectSettingsButton = new IconButton ("Project Settings", &icons.settings));
  350. projectSettingsButton->addListener (this);
  351. addAndMakeVisible (continuousRebuildButton = new IconButton ("Continuous Rebuild", &icons.continuousBuildStart));
  352. continuousRebuildButton->addListener (this);
  353. addAndMakeVisible (buildNowButton = new IconButton ("Build Now", &icons.buildNow));
  354. buildNowButton->addListener (this);
  355. addAndMakeVisible (exporterSettingsButton = new IconButton ("Exporter Settings", &icons.edit));
  356. exporterSettingsButton->addListener (this);
  357. addAndMakeVisible (saveAndOpenInIDEButton = new IconButton ("Save and Open in IDE", nullptr));
  358. saveAndOpenInIDEButton->addListener (this);
  359. saveAndOpenInIDEButton->isIDEButton = true;
  360. addAndMakeVisible (userSettingsButton = new IconButton ("User Settings", &icons.user));
  361. userSettingsButton->addListener (this);
  362. userSettingsButton->isUserButton = true;
  363. updateExporterButton();
  364. updateUserAvatar();
  365. }
  366. void updateIfNeeded (ValueTree tree)
  367. {
  368. if (tree == exportersTree)
  369. updateExporters();
  370. }
  371. void updateName()
  372. {
  373. projectNameLabel.setText (project->getDocumentTitle(), dontSendNotification);
  374. }
  375. void updateExporterButton()
  376. {
  377. auto currentExporterName = getSelectedExporterName();
  378. for (auto info : ProjectExporter::getExporterTypes())
  379. {
  380. if (currentExporterName.contains (info.name))
  381. {
  382. saveAndOpenInIDEButton->iconImage = info.getIcon();
  383. saveAndOpenInIDEButton->repaint();
  384. saveAndOpenInIDEButton->setEnabled (canCurrentExporterLaunchProject());
  385. }
  386. }
  387. }
  388. void updateUserAvatar()
  389. {
  390. if (LicenseController* controller = ProjucerApplication::getApp().licenseController)
  391. {
  392. auto state = controller->getState();
  393. userSettingsButton->iconImage = state.avatar;
  394. userSettingsButton->repaint();
  395. }
  396. }
  397. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HeaderComponent)
  398. };