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.

407 lines
13KB

  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->getName().contains (ProjectExporter::getCurrentPlatformExporterName()) && preferredExporterIndex == -1)
  126. preferredExporterIndex = i;
  127. }
  128. if (exporterBox.getSelectedItemIndex() == -1)
  129. {
  130. if (preferredExporterIndex == -1)
  131. {
  132. i = 0;
  133. for (Project::ExporterIterator exporter (*project); exporter.next(); ++i)
  134. {
  135. if (exporter->canLaunchProject())
  136. {
  137. preferredExporterIndex = i;
  138. break;
  139. }
  140. }
  141. }
  142. exporterBox.setSelectedItemIndex (preferredExporterIndex != -1 ? preferredExporterIndex : 0);
  143. }
  144. updateExporterButton();
  145. }
  146. String HeaderComponent::getSelectedExporterName() const noexcept
  147. {
  148. return exporterBox.getItemText (exporterBox.getSelectedItemIndex());
  149. }
  150. bool HeaderComponent::canCurrentExporterLaunchProject() const noexcept
  151. {
  152. for (Project::ExporterIterator exporter (*project); exporter.next();)
  153. if (exporter->getName() == getSelectedExporterName() && exporter->canLaunchProject())
  154. return true;
  155. return false;
  156. }
  157. //======================================================================
  158. int HeaderComponent::getUserButtonWidth() const noexcept
  159. {
  160. return userSettingsButton->getWidth();
  161. }
  162. void HeaderComponent::sidebarTabsWidthChanged (int newWidth) noexcept
  163. {
  164. tabsWidth = newWidth;
  165. resized();
  166. }
  167. //======================================================================
  168. void HeaderComponent::showUserSettings() noexcept
  169. {
  170. #if JUCER_ENABLE_GPL_MODE
  171. auto settingsPopupHeight = 100;
  172. auto settingsPopupWidth = 200;
  173. #else
  174. auto settingsPopupHeight = 150;
  175. auto settingsPopupWidth = 250;
  176. #endif
  177. auto* content = new UserSettingsPopup (false);
  178. content->setSize (settingsPopupWidth, settingsPopupHeight);
  179. userSettingsWindow = &CallOutBox::launchAsynchronously (content, userSettingsButton->getScreenBounds(), nullptr);
  180. }
  181. //==========================================================================
  182. void HeaderComponent::lookAndFeelChanged()
  183. {
  184. if (userSettingsWindow != nullptr)
  185. userSettingsWindow->sendLookAndFeelChange();
  186. }
  187. void HeaderComponent::changeListenerCallback (ChangeBroadcaster*)
  188. {
  189. if (childProcess != nullptr)
  190. {
  191. if (childProcess->activityList.getNumActivities() > 0)
  192. buildPing();
  193. else
  194. buildFinished (childProcess->errorList.getNumErrors() == 0);
  195. }
  196. }
  197. void HeaderComponent::valueChanged (Value&)
  198. {
  199. updateName();
  200. }
  201. void HeaderComponent::timerCallback()
  202. {
  203. repaint();
  204. }
  205. //======================================================================
  206. static void sendProjectButtonAnalyticsEvent (StringRef label)
  207. {
  208. StringPairArray data;
  209. data.set ("label", label);
  210. Analytics::getInstance()->logEvent ("Project Button", data, ProjucerAnalyticsEvent::projectEvent);
  211. }
  212. void HeaderComponent::initialiseButtons() noexcept
  213. {
  214. auto& icons = getIcons();
  215. projectSettingsButton.reset (new IconButton ("Project Settings", &icons.settings));
  216. addAndMakeVisible (projectSettingsButton.get());
  217. projectSettingsButton->onClick = [this]
  218. {
  219. sendProjectButtonAnalyticsEvent ("Project Settings");
  220. if (auto* pcc = findParentComponentOfClass<ProjectContentComponent>())
  221. pcc->showProjectSettings();
  222. };
  223. saveAndOpenInIDEButton.reset (new IconButton ("Save and Open in IDE", nullptr));
  224. addAndMakeVisible (saveAndOpenInIDEButton.get());
  225. saveAndOpenInIDEButton->isIDEButton = true;
  226. saveAndOpenInIDEButton->onClick = [this]
  227. {
  228. sendProjectButtonAnalyticsEvent ("Save and Open in IDE (" + exporterBox.getText() + ")");
  229. if (auto* pcc = findParentComponentOfClass<ProjectContentComponent>())
  230. pcc->openInSelectedIDE (true);
  231. };
  232. userSettingsButton.reset (new IconButton ("User Settings", &icons.user));
  233. addAndMakeVisible (userSettingsButton.get());
  234. userSettingsButton->isUserButton = true;
  235. userSettingsButton->onClick = [this]
  236. {
  237. sendProjectButtonAnalyticsEvent ("User Settings");
  238. if (findParentComponentOfClass<ProjectContentComponent>() != nullptr)
  239. showUserSettings();
  240. };
  241. runAppButton.reset (new IconButton ("Run Application", &icons.play));
  242. addAndMakeVisible (runAppButton.get());
  243. runAppButton->onClick = [this]
  244. {
  245. sendProjectButtonAnalyticsEvent ("Run Application");
  246. if (childProcess != nullptr)
  247. childProcess->launchApp();
  248. };
  249. updateExporterButton();
  250. updateUserAvatar();
  251. }
  252. void HeaderComponent::updateName() noexcept
  253. {
  254. projectNameLabel.setText (project->getDocumentTitle(), dontSendNotification);
  255. }
  256. void HeaderComponent::updateExporterButton() noexcept
  257. {
  258. auto currentExporterName = getSelectedExporterName();
  259. for (auto info : ProjectExporter::getExporterTypes())
  260. {
  261. if (currentExporterName.contains (info.name))
  262. {
  263. saveAndOpenInIDEButton->iconImage = info.getIcon();
  264. saveAndOpenInIDEButton->repaint();
  265. saveAndOpenInIDEButton->setEnabled (canCurrentExporterLaunchProject());
  266. }
  267. }
  268. }
  269. void HeaderComponent::updateUserAvatar() noexcept
  270. {
  271. if (auto* controller = ProjucerApplication::getApp().licenseController.get())
  272. {
  273. auto state = controller->getState();
  274. userSettingsButton->iconImage = state.avatar;
  275. userSettingsButton->repaint();
  276. }
  277. }
  278. //======================================================================
  279. void HeaderComponent::buildPing()
  280. {
  281. if (! isTimerRunning())
  282. {
  283. isBuilding = true;
  284. runAppButton->setEnabled (false);
  285. runAppButton->setTooltip ("Building...");
  286. startTimer (50);
  287. }
  288. }
  289. void HeaderComponent::buildFinished (bool success)
  290. {
  291. stopTimer();
  292. isBuilding = false;
  293. repaint();
  294. setRunAppButtonState (success);
  295. }
  296. void HeaderComponent::setRunAppButtonState (bool buildWasSuccessful)
  297. {
  298. bool shouldEnableButton = false;
  299. if (buildWasSuccessful)
  300. {
  301. if (childProcess != nullptr)
  302. {
  303. if (childProcess->isAppRunning() || (! childProcess->isAppRunning() && childProcess->canLaunchApp()))
  304. {
  305. runAppButton->setTooltip ("Launch application");
  306. shouldEnableButton = true;
  307. }
  308. else
  309. {
  310. runAppButton->setTooltip ("Application can't be launched");
  311. }
  312. }
  313. else
  314. {
  315. runAppButton->setTooltip ("Enable live-build engine to launch application");
  316. }
  317. }
  318. else
  319. {
  320. runAppButton->setTooltip ("Error building application");
  321. }
  322. runAppButton->setEnabled (shouldEnableButton);
  323. }