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.

393 lines
12KB

  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. #include "jucer_HeaderComponent.h"
  20. #include "../../ProjectSaving/jucer_ProjectExporter.h"
  21. #include "../../Project/UI/jucer_ProjectContentComponent.h"
  22. #include "../../LiveBuildEngine/jucer_MessageIDs.h"
  23. #include "../../LiveBuildEngine/jucer_SourceCodeRange.h"
  24. #include "../../LiveBuildEngine/jucer_ClassDatabase.h"
  25. #include "../../LiveBuildEngine/jucer_DiagnosticMessage.h"
  26. #include "../../LiveBuildEngine/jucer_CompileEngineClient.h"
  27. //======================================================================
  28. HeaderComponent::HeaderComponent()
  29. {
  30. addAndMakeVisible (configLabel);
  31. addAndMakeVisible (exporterBox);
  32. exporterBox.onChange = [this] { updateExporterButton(); };
  33. juceIcon.reset (new ImageComponent ("icon"));
  34. addAndMakeVisible (juceIcon.get());
  35. juceIcon->setImage (ImageCache::getFromMemory (BinaryData::juce_icon_png, BinaryData::juce_icon_pngSize),
  36. RectanglePlacement::centred);
  37. projectNameLabel.setText ({}, dontSendNotification);
  38. addAndMakeVisible (projectNameLabel);
  39. initialiseButtons();
  40. }
  41. HeaderComponent::~HeaderComponent()
  42. {
  43. if (userSettingsWindow != nullptr)
  44. userSettingsWindow->dismiss();
  45. if (childProcess != nullptr)
  46. {
  47. childProcess->activityList.removeChangeListener(this);
  48. childProcess->errorList.removeChangeListener (this);
  49. }
  50. }
  51. //======================================================================
  52. void HeaderComponent::resized()
  53. {
  54. auto bounds = getLocalBounds();
  55. configLabel.setFont ({ bounds.getHeight() / 3.0f });
  56. //======================================================================
  57. {
  58. auto headerBounds = bounds.removeFromLeft (tabsWidth);
  59. const int buttonSize = 25;
  60. auto buttonBounds = headerBounds.removeFromRight (buttonSize);
  61. projectSettingsButton->setBounds (buttonBounds.removeFromBottom (buttonSize).reduced (2));
  62. juceIcon->setBounds (headerBounds.removeFromLeft (headerBounds.getHeight()).reduced (2));
  63. headerBounds.removeFromRight (5);
  64. projectNameLabel.setBounds (headerBounds);
  65. }
  66. //======================================================================
  67. auto exporterWidth = jmin (400, bounds.getWidth() / 2);
  68. Rectangle<int> exporterBounds (0, 0, exporterWidth, bounds.getHeight());
  69. exporterBounds.setCentre (bounds.getCentre());
  70. runAppButton->setBounds (exporterBounds.removeFromRight (exporterBounds.getHeight()).reduced (2));
  71. saveAndOpenInIDEButton->setBounds (exporterBounds.removeFromRight (exporterBounds.getHeight()).reduced (2));
  72. exporterBounds.removeFromRight (5);
  73. exporterBox.setBounds (exporterBounds.removeFromBottom (roundToInt (exporterBounds.getHeight() / 1.8f)));
  74. configLabel.setBounds (exporterBounds);
  75. bounds.removeFromRight (5);
  76. userSettingsButton->setBounds (bounds.removeFromRight (bounds.getHeight()).reduced (2));
  77. }
  78. void HeaderComponent::paint (Graphics& g)
  79. {
  80. g.fillAll (findColour (backgroundColourId));
  81. if (isBuilding)
  82. getLookAndFeel().drawSpinningWaitAnimation (g, findColour (treeIconColourId),
  83. runAppButton->getX(), runAppButton->getY(),
  84. runAppButton->getWidth(), runAppButton->getHeight());
  85. }
  86. //======================================================================
  87. void HeaderComponent::setCurrentProject (Project* p) noexcept
  88. {
  89. project = p;
  90. exportersTree = project->getExporters();
  91. exportersTree.addListener (this);
  92. updateExporters();
  93. projectNameValue.referTo (project->getProjectValue (Ids::name));
  94. projectNameValue.addListener (this);
  95. updateName();
  96. isBuilding = false;
  97. stopTimer();
  98. repaint();
  99. childProcess = ProjucerApplication::getApp().childProcessCache->getExisting (*project);
  100. if (childProcess != nullptr)
  101. {
  102. childProcess->activityList.addChangeListener (this);
  103. childProcess->errorList.addChangeListener (this);
  104. runAppButton->setTooltip ({});
  105. runAppButton->setEnabled (true);
  106. }
  107. else
  108. {
  109. runAppButton->setTooltip ("Enable live-build engine to launch application");
  110. runAppButton->setEnabled (false);
  111. }
  112. }
  113. //======================================================================
  114. void HeaderComponent::updateExporters() noexcept
  115. {
  116. auto selectedName = getSelectedExporterName();
  117. exporterBox.clear();
  118. auto preferredExporterIndex = -1;
  119. int i = 0;
  120. for (Project::ExporterIterator exporter (*project); exporter.next(); ++i)
  121. {
  122. exporterBox.addItem (exporter->getName(), i + 1);
  123. if (selectedName == exporter->getName())
  124. exporterBox.setSelectedId (i + 1);
  125. if (exporter->canLaunchProject() && preferredExporterIndex == -1)
  126. preferredExporterIndex = i;
  127. }
  128. if (exporterBox.getSelectedItemIndex() == -1)
  129. exporterBox.setSelectedItemIndex (preferredExporterIndex != -1 ? preferredExporterIndex
  130. : 0);
  131. updateExporterButton();
  132. }
  133. String HeaderComponent::getSelectedExporterName() const noexcept
  134. {
  135. return exporterBox.getItemText (exporterBox.getSelectedItemIndex());
  136. }
  137. bool HeaderComponent::canCurrentExporterLaunchProject() const noexcept
  138. {
  139. for (Project::ExporterIterator exporter (*project); exporter.next();)
  140. if (exporter->getName() == getSelectedExporterName() && exporter->canLaunchProject())
  141. return true;
  142. return false;
  143. }
  144. //======================================================================
  145. int HeaderComponent::getUserButtonWidth() const noexcept
  146. {
  147. return userSettingsButton->getWidth();
  148. }
  149. void HeaderComponent::sidebarTabsWidthChanged (int newWidth) noexcept
  150. {
  151. tabsWidth = newWidth;
  152. resized();
  153. }
  154. //======================================================================
  155. void HeaderComponent::showUserSettings() noexcept
  156. {
  157. #if JUCER_ENABLE_GPL_MODE
  158. auto settingsPopupHeight = 100;
  159. auto settingsPopupWidth = 200;
  160. #else
  161. auto settingsPopupHeight = 150;
  162. auto settingsPopupWidth = 250;
  163. #endif
  164. auto* content = new UserSettingsPopup (false);
  165. content->setSize (settingsPopupWidth, settingsPopupHeight);
  166. userSettingsWindow = &CallOutBox::launchAsynchronously (content, userSettingsButton->getScreenBounds(), nullptr);
  167. }
  168. //==========================================================================
  169. void HeaderComponent::lookAndFeelChanged()
  170. {
  171. if (userSettingsWindow != nullptr)
  172. userSettingsWindow->sendLookAndFeelChange();
  173. }
  174. void HeaderComponent::changeListenerCallback (ChangeBroadcaster*)
  175. {
  176. if (childProcess != nullptr)
  177. {
  178. if (childProcess->activityList.getNumActivities() > 0)
  179. buildPing();
  180. else
  181. buildFinished (childProcess->errorList.getNumErrors() == 0);
  182. }
  183. }
  184. void HeaderComponent::valueChanged (Value&)
  185. {
  186. updateName();
  187. }
  188. void HeaderComponent::timerCallback()
  189. {
  190. repaint();
  191. }
  192. //======================================================================
  193. static void sendProjectButtonAnalyticsEvent (StringRef label)
  194. {
  195. StringPairArray data;
  196. data.set ("label", label);
  197. Analytics::getInstance()->logEvent ("Project Button", data, ProjucerAnalyticsEvent::projectEvent);
  198. }
  199. void HeaderComponent::initialiseButtons() noexcept
  200. {
  201. auto& icons = getIcons();
  202. projectSettingsButton.reset (new IconButton ("Project Settings", &icons.settings));
  203. addAndMakeVisible (projectSettingsButton.get());
  204. projectSettingsButton->onClick = [this]
  205. {
  206. sendProjectButtonAnalyticsEvent ("Project Settings");
  207. if (auto* pcc = findParentComponentOfClass<ProjectContentComponent>())
  208. pcc->showProjectSettings();
  209. };
  210. saveAndOpenInIDEButton.reset (new IconButton ("Save and Open in IDE", nullptr));
  211. addAndMakeVisible (saveAndOpenInIDEButton.get());
  212. saveAndOpenInIDEButton->isIDEButton = true;
  213. saveAndOpenInIDEButton->onClick = [this]
  214. {
  215. sendProjectButtonAnalyticsEvent ("Save and Open in IDE (" + exporterBox.getText() + ")");
  216. if (auto* pcc = findParentComponentOfClass<ProjectContentComponent>())
  217. pcc->openInSelectedIDE (true);
  218. };
  219. userSettingsButton.reset (new IconButton ("User Settings", &icons.user));
  220. addAndMakeVisible (userSettingsButton.get());
  221. userSettingsButton->isUserButton = true;
  222. userSettingsButton->onClick = [this]
  223. {
  224. sendProjectButtonAnalyticsEvent ("User Settings");
  225. if (findParentComponentOfClass<ProjectContentComponent>() != nullptr)
  226. showUserSettings();
  227. };
  228. runAppButton.reset (new IconButton ("Run Application", &icons.play));
  229. addAndMakeVisible (runAppButton.get());
  230. runAppButton->onClick = [this]
  231. {
  232. sendProjectButtonAnalyticsEvent ("Run Application");
  233. if (childProcess != nullptr)
  234. childProcess->launchApp();
  235. };
  236. updateExporterButton();
  237. updateUserAvatar();
  238. }
  239. void HeaderComponent::updateName() noexcept
  240. {
  241. projectNameLabel.setText (project->getDocumentTitle(), dontSendNotification);
  242. }
  243. void HeaderComponent::updateExporterButton() noexcept
  244. {
  245. auto currentExporterName = getSelectedExporterName();
  246. for (auto info : ProjectExporter::getExporterTypes())
  247. {
  248. if (currentExporterName.contains (info.name))
  249. {
  250. saveAndOpenInIDEButton->iconImage = info.getIcon();
  251. saveAndOpenInIDEButton->repaint();
  252. saveAndOpenInIDEButton->setEnabled (canCurrentExporterLaunchProject());
  253. }
  254. }
  255. }
  256. void HeaderComponent::updateUserAvatar() noexcept
  257. {
  258. if (auto* controller = ProjucerApplication::getApp().licenseController.get())
  259. {
  260. auto state = controller->getState();
  261. userSettingsButton->iconImage = state.avatar;
  262. userSettingsButton->repaint();
  263. }
  264. }
  265. //======================================================================
  266. void HeaderComponent::buildPing()
  267. {
  268. if (! isTimerRunning())
  269. {
  270. isBuilding = true;
  271. runAppButton->setEnabled (false);
  272. runAppButton->setTooltip ("Building...");
  273. startTimer (50);
  274. }
  275. }
  276. void HeaderComponent::buildFinished (bool success)
  277. {
  278. stopTimer();
  279. isBuilding = false;
  280. repaint();
  281. setRunAppButtonState (success);
  282. }
  283. void HeaderComponent::setRunAppButtonState (bool buildWasSuccessful)
  284. {
  285. bool shouldEnableButton = false;
  286. if (buildWasSuccessful)
  287. {
  288. if (childProcess != nullptr)
  289. {
  290. if (childProcess->isAppRunning() || (! childProcess->isAppRunning() && childProcess->canLaunchApp()))
  291. {
  292. runAppButton->setTooltip ("Launch application");
  293. shouldEnableButton = true;
  294. }
  295. else
  296. {
  297. runAppButton->setTooltip ("Application can't be launched");
  298. }
  299. }
  300. else
  301. {
  302. runAppButton->setTooltip ("Enable live-build engine to launch application");
  303. }
  304. }
  305. else
  306. {
  307. runAppButton->setTooltip ("Error building application");
  308. }
  309. runAppButton->setEnabled (shouldEnableButton);
  310. }