Audio plugin host https://kx.studio/carla
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.

juce_CallOutBox.cpp 7.8KB

9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2015 - ROLI Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. CallOutBox::CallOutBox (Component& c, const Rectangle<int>& area, Component* const parent)
  18. : arrowSize (16.0f), content (c), dismissalMouseClicksAreAlwaysConsumed (false)
  19. {
  20. addAndMakeVisible (content);
  21. if (parent != nullptr)
  22. {
  23. parent->addChildComponent (this);
  24. updatePosition (area, parent->getLocalBounds());
  25. setVisible (true);
  26. }
  27. else
  28. {
  29. setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows());
  30. updatePosition (area, Desktop::getInstance().getDisplays()
  31. .getDisplayContaining (area.getCentre()).userArea);
  32. addToDesktop (ComponentPeer::windowIsTemporary);
  33. }
  34. }
  35. CallOutBox::~CallOutBox()
  36. {
  37. }
  38. //==============================================================================
  39. class CallOutBoxCallback : public ModalComponentManager::Callback,
  40. private Timer
  41. {
  42. public:
  43. CallOutBoxCallback (Component* c, const Rectangle<int>& area, Component* parent)
  44. : content (c), callout (*c, area, parent)
  45. {
  46. callout.setVisible (true);
  47. callout.enterModalState (true, this);
  48. startTimer (200);
  49. }
  50. void modalStateFinished (int) override {}
  51. void timerCallback() override
  52. {
  53. if (! Process::isForegroundProcess())
  54. callout.dismiss();
  55. }
  56. ScopedPointer<Component> content;
  57. CallOutBox callout;
  58. JUCE_DECLARE_NON_COPYABLE (CallOutBoxCallback)
  59. };
  60. CallOutBox& CallOutBox::launchAsynchronously (Component* content, const Rectangle<int>& area, Component* parent)
  61. {
  62. jassert (content != nullptr); // must be a valid content component!
  63. return (new CallOutBoxCallback (content, area, parent))->callout;
  64. }
  65. //==============================================================================
  66. void CallOutBox::setArrowSize (const float newSize)
  67. {
  68. arrowSize = newSize;
  69. refreshPath();
  70. }
  71. int CallOutBox::getBorderSize() const noexcept
  72. {
  73. return jmax (getLookAndFeel().getCallOutBoxBorderSize (*this), (int) arrowSize);
  74. }
  75. void CallOutBox::paint (Graphics& g)
  76. {
  77. getLookAndFeel().drawCallOutBoxBackground (*this, g, outline, background);
  78. }
  79. void CallOutBox::resized()
  80. {
  81. const int borderSpace = getBorderSize();
  82. content.setTopLeftPosition (borderSpace, borderSpace);
  83. refreshPath();
  84. }
  85. void CallOutBox::moved()
  86. {
  87. refreshPath();
  88. }
  89. void CallOutBox::childBoundsChanged (Component*)
  90. {
  91. updatePosition (targetArea, availableArea);
  92. }
  93. bool CallOutBox::hitTest (int x, int y)
  94. {
  95. return outline.contains ((float) x, (float) y);
  96. }
  97. void CallOutBox::inputAttemptWhenModal()
  98. {
  99. if (dismissalMouseClicksAreAlwaysConsumed
  100. || targetArea.contains (getMouseXYRelative() + getBounds().getPosition()))
  101. {
  102. // if you click on the area that originally popped-up the callout, you expect it
  103. // to get rid of the box, but deleting the box here allows the click to pass through and
  104. // probably re-trigger it, so we need to dismiss the box asynchronously to consume the click..
  105. dismiss();
  106. }
  107. else
  108. {
  109. exitModalState (0);
  110. setVisible (false);
  111. }
  112. }
  113. void CallOutBox::setDismissalMouseClicksAreAlwaysConsumed (bool b) noexcept
  114. {
  115. dismissalMouseClicksAreAlwaysConsumed = b;
  116. }
  117. enum { callOutBoxDismissCommandId = 0x4f83a04b };
  118. void CallOutBox::handleCommandMessage (int commandId)
  119. {
  120. Component::handleCommandMessage (commandId);
  121. if (commandId == callOutBoxDismissCommandId)
  122. {
  123. exitModalState (0);
  124. setVisible (false);
  125. }
  126. }
  127. void CallOutBox::dismiss()
  128. {
  129. postCommandMessage (callOutBoxDismissCommandId);
  130. }
  131. bool CallOutBox::keyPressed (const KeyPress& key)
  132. {
  133. if (key.isKeyCode (KeyPress::escapeKey))
  134. {
  135. inputAttemptWhenModal();
  136. return true;
  137. }
  138. return false;
  139. }
  140. void CallOutBox::updatePosition (const Rectangle<int>& newAreaToPointTo, const Rectangle<int>& newAreaToFitIn)
  141. {
  142. targetArea = newAreaToPointTo;
  143. availableArea = newAreaToFitIn;
  144. const int borderSpace = getBorderSize();
  145. Rectangle<int> newBounds (content.getWidth() + borderSpace * 2,
  146. content.getHeight() + borderSpace * 2);
  147. const int hw = newBounds.getWidth() / 2;
  148. const int hh = newBounds.getHeight() / 2;
  149. const float hwReduced = (float) (hw - borderSpace * 2);
  150. const float hhReduced = (float) (hh - borderSpace * 2);
  151. const float arrowIndent = borderSpace - arrowSize;
  152. Point<float> targets[4] = { Point<float> ((float) targetArea.getCentreX(), (float) targetArea.getBottom()),
  153. Point<float> ((float) targetArea.getRight(), (float) targetArea.getCentreY()),
  154. Point<float> ((float) targetArea.getX(), (float) targetArea.getCentreY()),
  155. Point<float> ((float) targetArea.getCentreX(), (float) targetArea.getY()) };
  156. Line<float> lines[4] = { Line<float> (targets[0].translated (-hwReduced, hh - arrowIndent), targets[0].translated (hwReduced, hh - arrowIndent)),
  157. Line<float> (targets[1].translated (hw - arrowIndent, -hhReduced), targets[1].translated (hw - arrowIndent, hhReduced)),
  158. Line<float> (targets[2].translated (-(hw - arrowIndent), -hhReduced), targets[2].translated (-(hw - arrowIndent), hhReduced)),
  159. Line<float> (targets[3].translated (-hwReduced, -(hh - arrowIndent)), targets[3].translated (hwReduced, -(hh - arrowIndent))) };
  160. const Rectangle<float> centrePointArea (newAreaToFitIn.reduced (hw, hh).toFloat());
  161. const Point<float> targetCentre (targetArea.getCentre().toFloat());
  162. float nearest = 1.0e9f;
  163. for (int i = 0; i < 4; ++i)
  164. {
  165. Line<float> constrainedLine (centrePointArea.getConstrainedPoint (lines[i].getStart()),
  166. centrePointArea.getConstrainedPoint (lines[i].getEnd()));
  167. const Point<float> centre (constrainedLine.findNearestPointTo (targetCentre));
  168. float distanceFromCentre = centre.getDistanceFrom (targets[i]);
  169. if (! centrePointArea.intersects (lines[i]))
  170. distanceFromCentre += 1000.0f;
  171. if (distanceFromCentre < nearest)
  172. {
  173. nearest = distanceFromCentre;
  174. targetPoint = targets[i];
  175. newBounds.setPosition ((int) (centre.x - hw),
  176. (int) (centre.y - hh));
  177. }
  178. }
  179. setBounds (newBounds);
  180. }
  181. void CallOutBox::refreshPath()
  182. {
  183. repaint();
  184. background = Image::null;
  185. outline.clear();
  186. const float gap = 4.5f;
  187. outline.addBubble (content.getBounds().toFloat().expanded (gap, gap),
  188. getLocalBounds().toFloat(),
  189. targetPoint - getPosition().toFloat(),
  190. 9.0f, arrowSize * 0.7f);
  191. }