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.

503 lines
17KB

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