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.

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