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.

257 lines
8.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. BEGIN_JUCE_NAMESPACE
  19. //==============================================================================
  20. CallOutBox::CallOutBox (Component& contentComponent,
  21. Component& componentToPointTo,
  22. Component* const parent)
  23. : borderSpace (20), arrowSize (16.0f), content (contentComponent)
  24. {
  25. addAndMakeVisible (&content);
  26. if (parent != nullptr)
  27. {
  28. parent->addChildComponent (this);
  29. updatePosition (parent->getLocalArea (&componentToPointTo, componentToPointTo.getLocalBounds()),
  30. parent->getLocalBounds());
  31. setVisible (true);
  32. }
  33. else
  34. {
  35. if (! JUCEApplication::isStandaloneApp())
  36. setAlwaysOnTop (true); // for a plugin, make it always-on-top because the host windows are often top-level
  37. updatePosition (componentToPointTo.getScreenBounds(),
  38. componentToPointTo.getParentMonitorArea());
  39. addToDesktop (ComponentPeer::windowIsTemporary);
  40. }
  41. }
  42. CallOutBox::~CallOutBox()
  43. {
  44. }
  45. //==============================================================================
  46. void CallOutBox::setArrowSize (const float newSize)
  47. {
  48. arrowSize = newSize;
  49. borderSpace = jmax (20, (int) arrowSize);
  50. refreshPath();
  51. }
  52. void CallOutBox::paint (Graphics& g)
  53. {
  54. if (background.isNull())
  55. {
  56. background = Image (Image::ARGB, getWidth(), getHeight(), true);
  57. Graphics g2 (background);
  58. getLookAndFeel().drawCallOutBoxBackground (*this, g2, outline);
  59. }
  60. g.setColour (Colours::black);
  61. g.drawImageAt (background, 0, 0);
  62. }
  63. void CallOutBox::resized()
  64. {
  65. content.setTopLeftPosition (borderSpace, borderSpace);
  66. refreshPath();
  67. }
  68. void CallOutBox::moved()
  69. {
  70. refreshPath();
  71. }
  72. void CallOutBox::childBoundsChanged (Component*)
  73. {
  74. updatePosition (targetArea, availableArea);
  75. }
  76. bool CallOutBox::hitTest (int x, int y)
  77. {
  78. return outline.contains ((float) x, (float) y);
  79. }
  80. enum { callOutBoxDismissCommandId = 0x4f83a04b };
  81. void CallOutBox::inputAttemptWhenModal()
  82. {
  83. const Point<int> mousePos (getMouseXYRelative() + getBounds().getPosition());
  84. if (targetArea.contains (mousePos))
  85. {
  86. // if you click on the area that originally popped-up the callout, you expect it
  87. // to get rid of the box, but deleting the box here allows the click to pass through and
  88. // probably re-trigger it, so we need to dismiss the box asynchronously to consume the click..
  89. postCommandMessage (callOutBoxDismissCommandId);
  90. }
  91. else
  92. {
  93. exitModalState (0);
  94. setVisible (false);
  95. }
  96. }
  97. void CallOutBox::handleCommandMessage (int commandId)
  98. {
  99. Component::handleCommandMessage (commandId);
  100. if (commandId == callOutBoxDismissCommandId)
  101. {
  102. exitModalState (0);
  103. setVisible (false);
  104. }
  105. }
  106. bool CallOutBox::keyPressed (const KeyPress& key)
  107. {
  108. if (key.isKeyCode (KeyPress::escapeKey))
  109. {
  110. inputAttemptWhenModal();
  111. return true;
  112. }
  113. return false;
  114. }
  115. void CallOutBox::updatePosition (const Rectangle<int>& newAreaToPointTo, const Rectangle<int>& newAreaToFitIn)
  116. {
  117. targetArea = newAreaToPointTo;
  118. availableArea = newAreaToFitIn;
  119. Rectangle<int> newBounds (0, 0,
  120. content.getWidth() + borderSpace * 2,
  121. content.getHeight() + borderSpace * 2);
  122. const int hw = newBounds.getWidth() / 2;
  123. const int hh = newBounds.getHeight() / 2;
  124. const float hwReduced = (float) (hw - borderSpace * 3);
  125. const float hhReduced = (float) (hh - borderSpace * 3);
  126. const float arrowIndent = borderSpace - arrowSize;
  127. Point<float> targets[4] = { Point<float> ((float) targetArea.getCentreX(), (float) targetArea.getBottom()),
  128. Point<float> ((float) targetArea.getRight(), (float) targetArea.getCentreY()),
  129. Point<float> ((float) targetArea.getX(), (float) targetArea.getCentreY()),
  130. Point<float> ((float) targetArea.getCentreX(), (float) targetArea.getY()) };
  131. Line<float> lines[4] = { Line<float> (targets[0].translated (-hwReduced, hh - arrowIndent), targets[0].translated (hwReduced, hh - arrowIndent)),
  132. Line<float> (targets[1].translated (hw - arrowIndent, -hhReduced), targets[1].translated (hw - arrowIndent, hhReduced)),
  133. Line<float> (targets[2].translated (-(hw - arrowIndent), -hhReduced), targets[2].translated (-(hw - arrowIndent), hhReduced)),
  134. Line<float> (targets[3].translated (-hwReduced, -(hh - arrowIndent)), targets[3].translated (hwReduced, -(hh - arrowIndent))) };
  135. const Rectangle<float> centrePointArea (newAreaToFitIn.reduced (hw, hh).toFloat());
  136. float nearest = 1.0e9f;
  137. for (int i = 0; i < 4; ++i)
  138. {
  139. Line<float> constrainedLine (centrePointArea.getConstrainedPoint (lines[i].getStart()),
  140. centrePointArea.getConstrainedPoint (lines[i].getEnd()));
  141. const Point<float> centre (constrainedLine.findNearestPointTo (centrePointArea.getCentre()));
  142. float distanceFromCentre = centre.getDistanceFrom (centrePointArea.getCentre());
  143. if (! (centrePointArea.contains (lines[i].getStart()) || centrePointArea.contains (lines[i].getEnd())))
  144. distanceFromCentre *= 2.0f;
  145. if (distanceFromCentre < nearest)
  146. {
  147. nearest = distanceFromCentre;
  148. targetPoint = targets[i];
  149. newBounds.setPosition ((int) (centre.getX() - hw),
  150. (int) (centre.getY() - hh));
  151. }
  152. }
  153. setBounds (newBounds);
  154. }
  155. void CallOutBox::refreshPath()
  156. {
  157. repaint();
  158. background = Image::null;
  159. outline.clear();
  160. const float gap = 4.5f;
  161. const float cornerSize = 9.0f;
  162. const float cornerSize2 = 2.0f * cornerSize;
  163. const float arrowBaseWidth = arrowSize * 0.7f;
  164. const float left = content.getX() - gap, top = content.getY() - gap, right = content.getRight() + gap, bottom = content.getBottom() + gap;
  165. const float targetX = targetPoint.getX() - getX(), targetY = targetPoint.getY() - getY();
  166. outline.startNewSubPath (left + cornerSize, top);
  167. if (targetY <= top)
  168. {
  169. outline.lineTo (targetX - arrowBaseWidth, top);
  170. outline.lineTo (targetX, targetY);
  171. outline.lineTo (targetX + arrowBaseWidth, top);
  172. }
  173. outline.lineTo (right - cornerSize, top);
  174. outline.addArc (right - cornerSize2, top, cornerSize2, cornerSize2, 0, float_Pi * 0.5f);
  175. if (targetX >= right)
  176. {
  177. outline.lineTo (right, targetY - arrowBaseWidth);
  178. outline.lineTo (targetX, targetY);
  179. outline.lineTo (right, targetY + arrowBaseWidth);
  180. }
  181. outline.lineTo (right, bottom - cornerSize);
  182. outline.addArc (right - cornerSize2, bottom - cornerSize2, cornerSize2, cornerSize2, float_Pi * 0.5f, float_Pi);
  183. if (targetY >= bottom)
  184. {
  185. outline.lineTo (targetX + arrowBaseWidth, bottom);
  186. outline.lineTo (targetX, targetY);
  187. outline.lineTo (targetX - arrowBaseWidth, bottom);
  188. }
  189. outline.lineTo (left + cornerSize, bottom);
  190. outline.addArc (left, bottom - cornerSize2, cornerSize2, cornerSize2, float_Pi, float_Pi * 1.5f);
  191. if (targetX <= left)
  192. {
  193. outline.lineTo (left, targetY + arrowBaseWidth);
  194. outline.lineTo (targetX, targetY);
  195. outline.lineTo (left, targetY - arrowBaseWidth);
  196. }
  197. outline.lineTo (left, top + cornerSize);
  198. outline.addArc (left, top, cornerSize2, cornerSize2, float_Pi * 1.5f, float_Pi * 2.0f - 0.05f);
  199. outline.closeSubPath();
  200. }
  201. END_JUCE_NAMESPACE