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.

556 lines
21KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2020 - Raw Material Software Limited
  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 6 End-User License
  8. Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
  9. End User License Agreement: www.juce.com/juce-6-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. #pragma once
  19. #include "../../Application/jucer_CommonHeaders.h"
  20. #include "../../Application/jucer_Application.h"
  21. //==============================================================================
  22. class MessagesPopupWindow : public Component,
  23. private ComponentMovementWatcher
  24. {
  25. public:
  26. MessagesPopupWindow (Component& target, Component& parent, Project& project)
  27. : ComponentMovementWatcher (&parent),
  28. targetComponent (target),
  29. parentComponent (parent),
  30. messagesListComponent (*this, project)
  31. {
  32. parentComponent.addAndMakeVisible (this);
  33. setAlwaysOnTop (true);
  34. addAndMakeVisible (viewport);
  35. viewport.setScrollBarsShown (true, false);
  36. viewport.setViewedComponent (&messagesListComponent, false);
  37. viewport.setWantsKeyboardFocus (false);
  38. setOpaque (true);
  39. }
  40. void paint (Graphics& g) override
  41. {
  42. g.fillAll (findColour (secondaryBackgroundColourId));
  43. }
  44. void resized() override
  45. {
  46. viewport.setBounds (getLocalBounds());
  47. }
  48. bool isListShowing() const
  49. {
  50. return messagesListComponent.getRequiredHeight() > 0;
  51. }
  52. void updateBounds (bool animate)
  53. {
  54. auto targetBounds = parentComponent.getLocalArea (&targetComponent, targetComponent.getLocalBounds());
  55. auto height = jmin (messagesListComponent.getRequiredHeight(), maxHeight);
  56. auto yPos = jmax (indent, targetBounds.getY() - height);
  57. Rectangle<int> bounds (targetBounds.getX(), yPos,
  58. jmin (width, parentComponent.getWidth() - targetBounds.getX() - indent), targetBounds.getY() - yPos);
  59. auto& animator = Desktop::getInstance().getAnimator();
  60. if (animate)
  61. {
  62. setBounds (bounds.withY (targetBounds.getY()));
  63. animator.animateComponent (this, bounds, 1.0f, 150, false, 1.0, 1.0);
  64. }
  65. else
  66. {
  67. if (animator.isAnimating (this))
  68. animator.cancelAnimation (this, false);
  69. setBounds (bounds);
  70. }
  71. messagesListComponent.resized();
  72. }
  73. private:
  74. //==============================================================================
  75. class MessagesListComponent : public Component,
  76. private ValueTree::Listener,
  77. private AsyncUpdater
  78. {
  79. public:
  80. MessagesListComponent (MessagesPopupWindow& o, Project& currentProject)
  81. : owner (o),
  82. project (currentProject)
  83. {
  84. messagesTree = project.getProjectMessages();
  85. messagesTree.addListener (this);
  86. setOpaque (true);
  87. messagesChanged();
  88. }
  89. void resized() override
  90. {
  91. auto bounds = getLocalBounds();
  92. auto numMessages = messages.size();
  93. for (size_t i = 0; i < numMessages; ++i)
  94. {
  95. messages[i]->setBounds (bounds.removeFromTop (messageHeight));
  96. if (numMessages > 1 && i != (numMessages - 1))
  97. bounds.removeFromTop (messageSpacing);
  98. }
  99. }
  100. void paint (Graphics& g) override
  101. {
  102. g.fillAll (findColour (backgroundColourId).contrasting (0.2f));
  103. }
  104. int getRequiredHeight() const
  105. {
  106. auto numMessages = (int) messages.size();
  107. if (numMessages > 0)
  108. return (numMessages * messageHeight) + ((numMessages - 1) * messageSpacing);
  109. return 0;
  110. }
  111. void updateSize (int parentWidth)
  112. {
  113. setSize (parentWidth, getRequiredHeight());
  114. }
  115. private:
  116. static constexpr int messageHeight = 65;
  117. static constexpr int messageSpacing = 2;
  118. //==============================================================================
  119. struct MessageComponent : public Component
  120. {
  121. MessageComponent (MessagesListComponent& listComponent,
  122. const Identifier& messageToDisplay,
  123. std::vector<ProjectMessages::MessageAction> messageActions)
  124. : message (messageToDisplay)
  125. {
  126. for (auto& action : messageActions)
  127. {
  128. auto button = std::make_unique<TextButton> (action.first);
  129. addAndMakeVisible (*button);
  130. button->onClick = action.second;
  131. buttons.push_back (std::move (button));
  132. }
  133. icon = (ProjectMessages::getTypeForMessage (message) == ProjectMessages::Ids::warning ? getIcons().warning : getIcons().info);
  134. messageTitleLabel.setText (ProjectMessages::getTitleForMessage (message), dontSendNotification);
  135. messageTitleLabel.setFont (Font (11.0f).boldened());
  136. addAndMakeVisible (messageTitleLabel);
  137. messageDescriptionLabel.setText (ProjectMessages::getDescriptionForMessage (message), dontSendNotification);
  138. messageDescriptionLabel.setFont (Font (11.0f));
  139. messageDescriptionLabel.setJustificationType (Justification::topLeft);
  140. addAndMakeVisible (messageDescriptionLabel);
  141. dismissButton.setShape (getLookAndFeel().getCrossShape (1.0f), false, true, false);
  142. addAndMakeVisible (dismissButton);
  143. dismissButton.onClick = [this, &listComponent]
  144. {
  145. listComponent.messagesTree.getChildWithName (ProjectMessages::getTypeForMessage (message))
  146. .getChildWithName (message)
  147. .setProperty (ProjectMessages::Ids::isVisible, false, nullptr);
  148. };
  149. }
  150. void paint (Graphics& g) override
  151. {
  152. g.fillAll (findColour (secondaryBackgroundColourId).contrasting (0.1f));
  153. auto bounds = getLocalBounds().reduced (5);
  154. g.setColour (findColour (defaultIconColourId));
  155. g.fillPath (icon, icon.getTransformToScaleToFit (bounds.removeFromTop (messageTitleHeight)
  156. .removeFromLeft (messageTitleHeight).toFloat(), true));
  157. }
  158. void resized() override
  159. {
  160. auto bounds = getLocalBounds().reduced (5);
  161. auto topSlice = bounds.removeFromTop (messageTitleHeight);
  162. topSlice.removeFromLeft (messageTitleHeight + 5);
  163. topSlice.removeFromRight (5);
  164. dismissButton.setBounds (topSlice.removeFromRight (messageTitleHeight));
  165. messageTitleLabel.setBounds (topSlice);
  166. bounds.removeFromTop (5);
  167. auto numButtons = (int) buttons.size();
  168. if (numButtons > 0)
  169. {
  170. auto buttonBounds = bounds.removeFromBottom (buttonHeight);
  171. auto buttonWidth = roundToInt ((float) buttonBounds.getWidth() / 3.5f);
  172. auto requiredWidth = (numButtons * buttonWidth) + ((numButtons - 1) * buttonSpacing);
  173. buttonBounds.reduce ((buttonBounds.getWidth() - requiredWidth) / 2, 0);
  174. for (auto& b : buttons)
  175. {
  176. b->setBounds (buttonBounds.removeFromLeft (buttonWidth));
  177. buttonBounds.removeFromLeft (buttonSpacing);
  178. }
  179. bounds.removeFromBottom (5);
  180. }
  181. messageDescriptionLabel.setBounds (bounds);
  182. }
  183. static constexpr int messageTitleHeight = 11;
  184. static constexpr int buttonHeight = messageHeight / 4;
  185. static constexpr int buttonSpacing = 5;
  186. Identifier message;
  187. Path icon;
  188. Label messageTitleLabel, messageDescriptionLabel;
  189. std::vector<std::unique_ptr<TextButton>> buttons;
  190. ShapeButton dismissButton { {},
  191. findColour (treeIconColourId),
  192. findColour (treeIconColourId).overlaidWith (findColour (defaultHighlightedTextColourId).withAlpha (0.2f)),
  193. findColour (treeIconColourId).overlaidWith (findColour (defaultHighlightedTextColourId).withAlpha (0.4f)) };
  194. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MessageComponent)
  195. };
  196. //==============================================================================
  197. void valueTreePropertyChanged (ValueTree&, const Identifier&) override { messagesChanged(); }
  198. void valueTreeChildAdded (ValueTree&, ValueTree&) override { messagesChanged(); }
  199. void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override { messagesChanged(); }
  200. void valueTreeChildOrderChanged (ValueTree&, int, int) override { messagesChanged(); }
  201. void valueTreeParentChanged (ValueTree&) override { messagesChanged(); }
  202. void valueTreeRedirected (ValueTree&) override { messagesChanged(); }
  203. void handleAsyncUpdate() override
  204. {
  205. messagesChanged();
  206. }
  207. void messagesChanged()
  208. {
  209. auto listWasShowing = (getHeight() > 0);
  210. auto warningsTree = messagesTree.getChildWithName (ProjectMessages::Ids::warning);
  211. auto notificationsTree = messagesTree.getChildWithName (ProjectMessages::Ids::notification);
  212. auto removePredicate = [warningsTree, notificationsTree] (std::unique_ptr<MessageComponent>& messageComponent)
  213. {
  214. for (int i = 0; i < warningsTree.getNumChildren(); ++i)
  215. {
  216. auto child = warningsTree.getChild (i);
  217. if (child.getType() == messageComponent->message
  218. && child.getProperty (ProjectMessages::Ids::isVisible))
  219. {
  220. return false;
  221. }
  222. }
  223. for (int i = 0; i < notificationsTree.getNumChildren(); ++i)
  224. {
  225. auto child = notificationsTree.getChild (i);
  226. if (child.getType() == messageComponent->message
  227. && child.getProperty (ProjectMessages::Ids::isVisible))
  228. {
  229. return false;
  230. }
  231. }
  232. return true;
  233. };
  234. messages.erase (std::remove_if (std::begin (messages), std::end (messages), removePredicate),
  235. std::end (messages));
  236. for (int i = 0; i < warningsTree.getNumChildren(); ++i)
  237. {
  238. auto child = warningsTree.getChild (i);
  239. if (! child.getProperty (ProjectMessages::Ids::isVisible))
  240. continue;
  241. if (std::find_if (std::begin (messages), std::end (messages),
  242. [child] (const std::unique_ptr<MessageComponent>& messageComponent) { return messageComponent->message == child.getType(); })
  243. == std::end (messages))
  244. {
  245. messages.push_back (std::make_unique<MessageComponent> (*this, child.getType(), project.getMessageActions (child.getType())));
  246. addAndMakeVisible (*messages.back());
  247. }
  248. }
  249. for (int i = 0; i < notificationsTree.getNumChildren(); ++i)
  250. {
  251. auto child = notificationsTree.getChild (i);
  252. if (! child.getProperty (ProjectMessages::Ids::isVisible))
  253. continue;
  254. if (std::find_if (std::begin (messages), std::end (messages),
  255. [child] (const std::unique_ptr<MessageComponent>& messageComponent) { return messageComponent->message == child.getType(); })
  256. == std::end (messages))
  257. {
  258. messages.push_back (std::make_unique<MessageComponent> (*this, child.getType(), project.getMessageActions (child.getType())));
  259. addAndMakeVisible (*messages.back());
  260. }
  261. }
  262. auto isNowShowing = (messages.size() > 0);
  263. owner.updateBounds (isNowShowing != listWasShowing);
  264. updateSize (owner.getWidth());
  265. }
  266. //==============================================================================
  267. MessagesPopupWindow& owner;
  268. Project& project;
  269. ValueTree messagesTree;
  270. std::vector<std::unique_ptr<MessageComponent>> messages;
  271. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MessagesListComponent)
  272. };
  273. //==============================================================================
  274. void componentMovedOrResized (bool, bool) override
  275. {
  276. if (isListShowing())
  277. updateBounds (false);
  278. }
  279. using ComponentMovementWatcher::componentMovedOrResized;
  280. void componentPeerChanged() override
  281. {
  282. if (isListShowing())
  283. updateBounds (false);
  284. }
  285. void componentVisibilityChanged() override
  286. {
  287. if (isListShowing())
  288. updateBounds (false);
  289. }
  290. using ComponentMovementWatcher::componentVisibilityChanged;
  291. //==============================================================================
  292. static constexpr int maxHeight = 500, width = 350, indent = 20;
  293. Component& targetComponent;
  294. Component& parentComponent;
  295. Viewport viewport;
  296. MessagesListComponent messagesListComponent;
  297. //==============================================================================
  298. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MessagesPopupWindow)
  299. };
  300. //==============================================================================
  301. class ProjectMessagesComponent : public Component
  302. {
  303. public:
  304. ProjectMessagesComponent()
  305. {
  306. addAndMakeVisible (warningsComponent);
  307. addAndMakeVisible (notificationsComponent);
  308. warningsComponent.addMouseListener (this, true);
  309. notificationsComponent.addMouseListener (this, true);
  310. setOpaque (true);
  311. }
  312. //==============================================================================
  313. void resized() override
  314. {
  315. auto b = getLocalBounds();
  316. warningsComponent.setBounds (b.removeFromLeft (b.getWidth() / 2).reduced (5));
  317. notificationsComponent.setBounds (b.reduced (5));
  318. }
  319. void paint (Graphics& g) override
  320. {
  321. auto backgroundColour = findColour (backgroundColourId);
  322. if (isMouseDown || isMouseOver)
  323. backgroundColour = backgroundColour.overlaidWith (findColour (defaultHighlightColourId)
  324. .withAlpha (isMouseDown ? 1.0f : 0.8f));
  325. g.fillAll (backgroundColour);
  326. }
  327. //==============================================================================
  328. void mouseEnter (const MouseEvent&) override
  329. {
  330. isMouseOver = true;
  331. repaint();
  332. }
  333. void mouseExit (const MouseEvent&) override
  334. {
  335. isMouseOver = false;
  336. repaint();
  337. }
  338. void mouseDown (const MouseEvent&) override
  339. {
  340. isMouseDown = true;
  341. repaint();
  342. }
  343. void mouseUp (const MouseEvent&) override
  344. {
  345. isMouseDown = false;
  346. repaint();
  347. if (messagesWindow != nullptr)
  348. showOrHideAllMessages (! messagesWindow->isListShowing());
  349. }
  350. //==============================================================================
  351. void setProject (Project* newProject)
  352. {
  353. if (currentProject != newProject)
  354. {
  355. currentProject = newProject;
  356. if (currentProject != nullptr)
  357. {
  358. auto* projectWindow = ProjucerApplication::getApp().mainWindowList.getMainWindowForFile (currentProject->getFile());
  359. jassert (projectWindow != nullptr);
  360. messagesWindow = std::make_unique<MessagesPopupWindow> (*this, *projectWindow, *currentProject);
  361. auto projectMessagesTree = currentProject->getProjectMessages();
  362. warningsComponent.setTree (projectMessagesTree.getChildWithName (ProjectMessages::Ids::warning));
  363. notificationsComponent.setTree (projectMessagesTree.getChildWithName (ProjectMessages::Ids::notification));
  364. }
  365. else
  366. {
  367. warningsComponent.setTree ({});
  368. notificationsComponent.setTree ({});
  369. }
  370. }
  371. }
  372. private:
  373. //==============================================================================
  374. struct MessageCountComponent : public Component,
  375. private ValueTree::Listener
  376. {
  377. MessageCountComponent (ProjectMessagesComponent& o, Path pathToUse)
  378. : owner (o),
  379. path (pathToUse)
  380. {
  381. setInterceptsMouseClicks (false, false);
  382. }
  383. void paint (Graphics& g) override
  384. {
  385. auto b = getLocalBounds().toFloat();
  386. g.setColour (findColour ((owner.isMouseDown || owner.isMouseOver) ? defaultHighlightedTextColourId : treeIconColourId));
  387. g.fillPath (path, path.getTransformToScaleToFit (b.removeFromLeft (b.getWidth() / 2.0f), true));
  388. b.removeFromLeft (5);
  389. g.drawFittedText (String (numMessages), b.getSmallestIntegerContainer(), Justification::centredLeft, 1);
  390. }
  391. void setTree (ValueTree tree)
  392. {
  393. messagesTree = tree;
  394. if (messagesTree.isValid())
  395. messagesTree.addListener (this);
  396. updateNumMessages();
  397. }
  398. void updateNumMessages()
  399. {
  400. numMessages = messagesTree.getNumChildren();
  401. repaint();
  402. }
  403. private:
  404. void valueTreeChildAdded (ValueTree&, ValueTree&) override { updateNumMessages(); }
  405. void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override { updateNumMessages(); }
  406. ProjectMessagesComponent& owner;
  407. ValueTree messagesTree;
  408. Path path;
  409. int numMessages = 0;
  410. };
  411. void showOrHideAllMessages (bool shouldBeVisible)
  412. {
  413. if (currentProject != nullptr)
  414. {
  415. auto messagesTree = currentProject->getProjectMessages();
  416. auto setVisible = [shouldBeVisible] (ValueTree subTree)
  417. {
  418. for (int i = 0; i < subTree.getNumChildren(); ++i)
  419. subTree.getChild (i).setProperty (ProjectMessages::Ids::isVisible, shouldBeVisible, nullptr);
  420. };
  421. setVisible (messagesTree.getChildWithName (ProjectMessages::Ids::warning));
  422. setVisible (messagesTree.getChildWithName (ProjectMessages::Ids::notification));
  423. }
  424. }
  425. //==============================================================================
  426. Project* currentProject = nullptr;
  427. bool isMouseOver = false, isMouseDown = false;
  428. MessageCountComponent warningsComponent { *this, getIcons().warning },
  429. notificationsComponent { *this, getIcons().info };
  430. std::unique_ptr<MessagesPopupWindow> messagesWindow;
  431. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectMessagesComponent)
  432. };