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.

256 lines
7.7KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - 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 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-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. namespace juce
  19. {
  20. TooltipWindow::TooltipWindow (Component* parentComp, int delayMs)
  21. : Component ("tooltip"),
  22. millisecondsBeforeTipAppears (delayMs)
  23. {
  24. setAlwaysOnTop (true);
  25. setOpaque (true);
  26. setAccessible (false);
  27. if (parentComp != nullptr)
  28. parentComp->addChildComponent (this);
  29. auto& desktop = Desktop::getInstance();
  30. if (desktop.getMainMouseSource().canHover())
  31. {
  32. desktop.addGlobalMouseListener (this);
  33. startTimer (123);
  34. }
  35. }
  36. TooltipWindow::~TooltipWindow()
  37. {
  38. hideTip();
  39. Desktop::getInstance().removeGlobalMouseListener (this);
  40. }
  41. void TooltipWindow::setMillisecondsBeforeTipAppears (const int newTimeMs) noexcept
  42. {
  43. millisecondsBeforeTipAppears = newTimeMs;
  44. }
  45. void TooltipWindow::paint (Graphics& g)
  46. {
  47. getLookAndFeel().drawTooltip (g, tipShowing, getWidth(), getHeight());
  48. }
  49. void TooltipWindow::mouseEnter (const MouseEvent& e)
  50. {
  51. if (e.eventComponent == this)
  52. hideTip();
  53. }
  54. void TooltipWindow::mouseDown (const MouseEvent&)
  55. {
  56. if (isVisible())
  57. dismissalMouseEventOccurred = true;
  58. }
  59. void TooltipWindow::mouseWheelMove (const MouseEvent&, const MouseWheelDetails&)
  60. {
  61. if (isVisible())
  62. dismissalMouseEventOccurred = true;
  63. }
  64. void TooltipWindow::updatePosition (const String& tip, Point<int> pos, Rectangle<int> parentArea)
  65. {
  66. setBounds (getLookAndFeel().getTooltipBounds (tip, pos, parentArea));
  67. setVisible (true);
  68. }
  69. #if JUCE_DEBUG
  70. static Array<TooltipWindow*> activeTooltipWindows;
  71. #endif
  72. void TooltipWindow::displayTip (Point<int> screenPos, const String& tip)
  73. {
  74. jassert (tip.isNotEmpty());
  75. displayTipInternal (screenPos, tip, ShownManually::yes);
  76. }
  77. void TooltipWindow::displayTipInternal (Point<int> screenPos, const String& tip, ShownManually shownManually)
  78. {
  79. if (! reentrant)
  80. {
  81. ScopedValueSetter<bool> setter (reentrant, true, false);
  82. if (tipShowing != tip)
  83. {
  84. tipShowing = tip;
  85. repaint();
  86. }
  87. if (auto* parent = getParentComponent())
  88. {
  89. updatePosition (tip, parent->getLocalPoint (nullptr, screenPos),
  90. parent->getLocalBounds());
  91. }
  92. else
  93. {
  94. const auto physicalPos = ScalingHelpers::scaledScreenPosToUnscaled (screenPos);
  95. const auto scaledPos = ScalingHelpers::unscaledScreenPosToScaled (*this, physicalPos);
  96. updatePosition (tip, scaledPos, Desktop::getInstance().getDisplays().getDisplayForPoint (screenPos)->userArea);
  97. addToDesktop (ComponentPeer::windowHasDropShadow
  98. | ComponentPeer::windowIsTemporary
  99. | ComponentPeer::windowIgnoresKeyPresses
  100. | ComponentPeer::windowIgnoresMouseClicks);
  101. }
  102. #if JUCE_DEBUG
  103. activeTooltipWindows.addIfNotAlreadyThere (this);
  104. auto* parent = getParentComponent();
  105. for (auto* w : activeTooltipWindows)
  106. {
  107. if (w != nullptr && w != this && w->tipShowing == tipShowing && w->getParentComponent() == parent)
  108. {
  109. // Looks like you have more than one TooltipWindow showing the same tip..
  110. // Be careful not to create more than one instance of this class with the
  111. // same parent component!
  112. jassertfalse;
  113. }
  114. }
  115. #endif
  116. toFront (false);
  117. manuallyShownTip = shownManually == ShownManually::yes ? tip : String();
  118. dismissalMouseEventOccurred = false;
  119. }
  120. }
  121. String TooltipWindow::getTipFor (Component& c)
  122. {
  123. if (isForegroundOrEmbeddedProcess (&c)
  124. && ! ModifierKeys::currentModifiers.isAnyMouseButtonDown())
  125. {
  126. if (auto* ttc = dynamic_cast<TooltipClient*> (&c))
  127. if (! c.isCurrentlyBlockedByAnotherModalComponent())
  128. return ttc->getTooltip();
  129. }
  130. return {};
  131. }
  132. void TooltipWindow::hideTip()
  133. {
  134. if (isVisible() && ! reentrant)
  135. {
  136. tipShowing = {};
  137. manuallyShownTip = {};
  138. dismissalMouseEventOccurred = false;
  139. removeFromDesktop();
  140. setVisible (false);
  141. lastHideTime = Time::getApproximateMillisecondCounter();
  142. #if JUCE_DEBUG
  143. activeTooltipWindows.removeAllInstancesOf (this);
  144. #endif
  145. }
  146. }
  147. float TooltipWindow::getDesktopScaleFactor() const
  148. {
  149. if (lastComponentUnderMouse != nullptr)
  150. return Component::getApproximateScaleFactorForComponent (lastComponentUnderMouse);
  151. return Component::getDesktopScaleFactor();
  152. }
  153. std::unique_ptr<AccessibilityHandler> TooltipWindow::createAccessibilityHandler()
  154. {
  155. return createIgnoredAccessibilityHandler (*this);
  156. }
  157. void TooltipWindow::timerCallback()
  158. {
  159. const auto mouseSource = Desktop::getInstance().getMainMouseSource();
  160. auto* newComp = mouseSource.isTouch() ? nullptr : mouseSource.getComponentUnderMouse();
  161. if (manuallyShownTip.isNotEmpty())
  162. {
  163. if (dismissalMouseEventOccurred || newComp == nullptr)
  164. hideTip();
  165. return;
  166. }
  167. if (newComp == nullptr || getParentComponent() == nullptr || newComp->getPeer() == getPeer())
  168. {
  169. const auto newTip = newComp != nullptr ? getTipFor (*newComp) : String();
  170. const auto mousePos = mouseSource.getScreenPosition();
  171. const auto mouseMovedQuickly = (mousePos.getDistanceFrom (lastMousePos) > 12);
  172. lastMousePos = mousePos;
  173. const auto tipChanged = (newTip != lastTipUnderMouse || newComp != lastComponentUnderMouse);
  174. const auto now = Time::getApproximateMillisecondCounter();
  175. lastComponentUnderMouse = newComp;
  176. lastTipUnderMouse = newTip;
  177. if (tipChanged || dismissalMouseEventOccurred || mouseMovedQuickly)
  178. lastCompChangeTime = now;
  179. const auto showTip = [this, &mouseSource, &mousePos, &newTip]
  180. {
  181. if (mouseSource.getLastMouseDownPosition() != lastMousePos)
  182. displayTipInternal (mousePos.roundToInt(), newTip, ShownManually::no);
  183. };
  184. if (isVisible() || now < lastHideTime + 500)
  185. {
  186. // if a tip is currently visible (or has just disappeared), update to a new one
  187. // immediately if needed..
  188. if (newComp == nullptr || dismissalMouseEventOccurred || newTip.isEmpty())
  189. hideTip();
  190. else if (tipChanged)
  191. showTip();
  192. }
  193. else
  194. {
  195. // if there isn't currently a tip, but one is needed, only let it appear after a timeout
  196. if (newTip.isNotEmpty()
  197. && newTip != tipShowing
  198. && now > lastCompChangeTime + (uint32) millisecondsBeforeTipAppears)
  199. {
  200. showTip();
  201. }
  202. }
  203. }
  204. }
  205. } // namespace juce