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.

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