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.

183 lines
5.6KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  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 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. TooltipWindow::TooltipWindow (Component* const parentComp, const int delayMs)
  20. : Component ("tooltip"),
  21. lastComponentUnderMouse (nullptr),
  22. millisecondsBeforeTipAppears (delayMs),
  23. mouseClicks (0), mouseWheelMoves (0),
  24. lastCompChangeTime (0), lastHideTime (0),
  25. reentrant (false)
  26. {
  27. if (Desktop::getInstance().getMainMouseSource().canHover())
  28. startTimer (123);
  29. setAlwaysOnTop (true);
  30. setOpaque (true);
  31. if (parentComp != nullptr)
  32. parentComp->addChildComponent (this);
  33. }
  34. TooltipWindow::~TooltipWindow()
  35. {
  36. hideTip();
  37. }
  38. void TooltipWindow::setMillisecondsBeforeTipAppears (const int newTimeMs) noexcept
  39. {
  40. millisecondsBeforeTipAppears = newTimeMs;
  41. }
  42. void TooltipWindow::paint (Graphics& g)
  43. {
  44. getLookAndFeel().drawTooltip (g, tipShowing, getWidth(), getHeight());
  45. }
  46. void TooltipWindow::mouseEnter (const MouseEvent&)
  47. {
  48. hideTip();
  49. }
  50. void TooltipWindow::updatePosition (const String& tip, Point<int> pos, Rectangle<int> parentArea)
  51. {
  52. setBounds (getLookAndFeel().getTooltipBounds (tip, pos, parentArea));
  53. setVisible (true);
  54. }
  55. void TooltipWindow::displayTip (Point<int> screenPos, const String& tip)
  56. {
  57. jassert (tip.isNotEmpty());
  58. if (! reentrant)
  59. {
  60. ScopedValueSetter<bool> setter (reentrant, true, false);
  61. if (tipShowing != tip)
  62. {
  63. tipShowing = tip;
  64. repaint();
  65. }
  66. if (auto* parent = getParentComponent())
  67. {
  68. updatePosition (tip, parent->getLocalPoint (nullptr, screenPos),
  69. parent->getLocalBounds());
  70. }
  71. else
  72. {
  73. updatePosition (tip, screenPos, Desktop::getInstance().getDisplays()
  74. .getDisplayContaining (screenPos).userArea);
  75. addToDesktop (ComponentPeer::windowHasDropShadow
  76. | ComponentPeer::windowIsTemporary
  77. | ComponentPeer::windowIgnoresKeyPresses
  78. | ComponentPeer::windowIgnoresMouseClicks);
  79. }
  80. toFront (false);
  81. }
  82. }
  83. String TooltipWindow::getTipFor (Component* const c)
  84. {
  85. if (c != nullptr
  86. && Process::isForegroundProcess()
  87. && ! ModifierKeys::getCurrentModifiers().isAnyMouseButtonDown())
  88. {
  89. if (TooltipClient* const ttc = dynamic_cast<TooltipClient*> (c))
  90. if (! c->isCurrentlyBlockedByAnotherModalComponent())
  91. return ttc->getTooltip();
  92. }
  93. return {};
  94. }
  95. void TooltipWindow::hideTip()
  96. {
  97. if (! reentrant)
  98. {
  99. tipShowing.clear();
  100. removeFromDesktop();
  101. setVisible (false);
  102. }
  103. }
  104. void TooltipWindow::timerCallback()
  105. {
  106. Desktop& desktop = Desktop::getInstance();
  107. const MouseInputSource mouseSource (desktop.getMainMouseSource());
  108. const unsigned int now = Time::getApproximateMillisecondCounter();
  109. Component* const newComp = ! mouseSource.isTouch() ? mouseSource.getComponentUnderMouse() : nullptr;
  110. const String newTip (getTipFor (newComp));
  111. const bool tipChanged = (newTip != lastTipUnderMouse || newComp != lastComponentUnderMouse);
  112. lastComponentUnderMouse = newComp;
  113. lastTipUnderMouse = newTip;
  114. const int clickCount = desktop.getMouseButtonClickCounter();
  115. const int wheelCount = desktop.getMouseWheelMoveCounter();
  116. const bool mouseWasClicked = (clickCount > mouseClicks || wheelCount > mouseWheelMoves);
  117. mouseClicks = clickCount;
  118. mouseWheelMoves = wheelCount;
  119. const Point<float> mousePos (mouseSource.getScreenPosition());
  120. const bool mouseMovedQuickly = mousePos.getDistanceFrom (lastMousePos) > 12;
  121. lastMousePos = mousePos;
  122. if (tipChanged || mouseWasClicked || mouseMovedQuickly)
  123. lastCompChangeTime = now;
  124. if (isVisible() || now < lastHideTime + 500)
  125. {
  126. // if a tip is currently visible (or has just disappeared), update to a new one
  127. // immediately if needed..
  128. if (newComp == nullptr || mouseWasClicked || newTip.isEmpty())
  129. {
  130. if (isVisible())
  131. {
  132. lastHideTime = now;
  133. hideTip();
  134. }
  135. }
  136. else if (tipChanged)
  137. {
  138. displayTip (mousePos.roundToInt(), newTip);
  139. }
  140. }
  141. else
  142. {
  143. // if there isn't currently a tip, but one is needed, only let it
  144. // appear after a timeout..
  145. if (newTip.isNotEmpty()
  146. && newTip != tipShowing
  147. && now > lastCompChangeTime + (unsigned int) millisecondsBeforeTipAppears)
  148. {
  149. displayTip (mousePos.roundToInt(), newTip);
  150. }
  151. }
  152. }