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.

200 lines
6.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. void CallOutBox::setArrowSize (const float newSize)
  45. {
  46. arrowSize = newSize;
  47. borderSpace = jmax (20, (int) arrowSize);
  48. refreshPath();
  49. }
  50. void CallOutBox::paint (Graphics& g)
  51. {
  52. getLookAndFeel().drawCallOutBoxBackground (*this, g, outline, background);
  53. }
  54. void CallOutBox::resized()
  55. {
  56. content.setTopLeftPosition (borderSpace, borderSpace);
  57. refreshPath();
  58. }
  59. void CallOutBox::moved()
  60. {
  61. refreshPath();
  62. }
  63. void CallOutBox::childBoundsChanged (Component*)
  64. {
  65. updatePosition (targetArea, availableArea);
  66. }
  67. bool CallOutBox::hitTest (int x, int y)
  68. {
  69. return outline.contains ((float) x, (float) y);
  70. }
  71. enum { callOutBoxDismissCommandId = 0x4f83a04b };
  72. void CallOutBox::inputAttemptWhenModal()
  73. {
  74. const Point<int> mousePos (getMouseXYRelative() + getBounds().getPosition());
  75. if (targetArea.contains (mousePos))
  76. {
  77. // if you click on the area that originally popped-up the callout, you expect it
  78. // to get rid of the box, but deleting the box here allows the click to pass through and
  79. // probably re-trigger it, so we need to dismiss the box asynchronously to consume the click..
  80. postCommandMessage (callOutBoxDismissCommandId);
  81. }
  82. else
  83. {
  84. exitModalState (0);
  85. setVisible (false);
  86. }
  87. }
  88. void CallOutBox::handleCommandMessage (int commandId)
  89. {
  90. Component::handleCommandMessage (commandId);
  91. if (commandId == callOutBoxDismissCommandId)
  92. {
  93. exitModalState (0);
  94. setVisible (false);
  95. }
  96. }
  97. bool CallOutBox::keyPressed (const KeyPress& key)
  98. {
  99. if (key.isKeyCode (KeyPress::escapeKey))
  100. {
  101. inputAttemptWhenModal();
  102. return true;
  103. }
  104. return false;
  105. }
  106. void CallOutBox::updatePosition (const Rectangle<int>& newAreaToPointTo, const Rectangle<int>& newAreaToFitIn)
  107. {
  108. targetArea = newAreaToPointTo;
  109. availableArea = newAreaToFitIn;
  110. Rectangle<int> newBounds (0, 0,
  111. content.getWidth() + borderSpace * 2,
  112. content.getHeight() + borderSpace * 2);
  113. const int hw = newBounds.getWidth() / 2;
  114. const int hh = newBounds.getHeight() / 2;
  115. const float hwReduced = (float) (hw - borderSpace * 3);
  116. const float hhReduced = (float) (hh - borderSpace * 3);
  117. const float arrowIndent = borderSpace - arrowSize;
  118. Point<float> targets[4] = { Point<float> ((float) targetArea.getCentreX(), (float) targetArea.getBottom()),
  119. Point<float> ((float) targetArea.getRight(), (float) targetArea.getCentreY()),
  120. Point<float> ((float) targetArea.getX(), (float) targetArea.getCentreY()),
  121. Point<float> ((float) targetArea.getCentreX(), (float) targetArea.getY()) };
  122. Line<float> lines[4] = { Line<float> (targets[0].translated (-hwReduced, hh - arrowIndent), targets[0].translated (hwReduced, hh - arrowIndent)),
  123. Line<float> (targets[1].translated (hw - arrowIndent, -hhReduced), targets[1].translated (hw - arrowIndent, hhReduced)),
  124. Line<float> (targets[2].translated (-(hw - arrowIndent), -hhReduced), targets[2].translated (-(hw - arrowIndent), hhReduced)),
  125. Line<float> (targets[3].translated (-hwReduced, -(hh - arrowIndent)), targets[3].translated (hwReduced, -(hh - arrowIndent))) };
  126. const Rectangle<float> centrePointArea (newAreaToFitIn.reduced (hw, hh).toFloat());
  127. float nearest = 1.0e9f;
  128. for (int i = 0; i < 4; ++i)
  129. {
  130. Line<float> constrainedLine (centrePointArea.getConstrainedPoint (lines[i].getStart()),
  131. centrePointArea.getConstrainedPoint (lines[i].getEnd()));
  132. const Point<float> centre (constrainedLine.findNearestPointTo (centrePointArea.getCentre()));
  133. float distanceFromCentre = centre.getDistanceFrom (centrePointArea.getCentre());
  134. if (! (centrePointArea.contains (lines[i].getStart()) || centrePointArea.contains (lines[i].getEnd())))
  135. distanceFromCentre *= 2.0f;
  136. if (distanceFromCentre < nearest)
  137. {
  138. nearest = distanceFromCentre;
  139. targetPoint = targets[i];
  140. newBounds.setPosition ((int) (centre.getX() - hw),
  141. (int) (centre.getY() - hh));
  142. }
  143. }
  144. setBounds (newBounds);
  145. }
  146. void CallOutBox::refreshPath()
  147. {
  148. repaint();
  149. background = Image::null;
  150. outline.clear();
  151. const float gap = 4.5f;
  152. outline.addBubble (content.getBounds().toFloat().expanded (gap, gap),
  153. getLocalBounds().toFloat(),
  154. targetPoint - getPosition().toFloat(),
  155. 9.0f, arrowSize * 0.7f);
  156. }