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.

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