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.

228 lines
7.9KB

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