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.

178 lines
5.3KB

  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* parentComp, int delayMs)
  20. : Component ("tooltip"),
  21. millisecondsBeforeTipAppears (delayMs)
  22. {
  23. setAlwaysOnTop (true);
  24. setOpaque (true);
  25. if (parentComp != nullptr)
  26. parentComp->addChildComponent (this);
  27. if (Desktop::getInstance().getMainMouseSource().canHover())
  28. startTimer (123);
  29. }
  30. TooltipWindow::~TooltipWindow()
  31. {
  32. hideTip();
  33. }
  34. void TooltipWindow::setMillisecondsBeforeTipAppears (const int newTimeMs) noexcept
  35. {
  36. millisecondsBeforeTipAppears = newTimeMs;
  37. }
  38. void TooltipWindow::paint (Graphics& g)
  39. {
  40. getLookAndFeel().drawTooltip (g, tipShowing, getWidth(), getHeight());
  41. }
  42. void TooltipWindow::mouseEnter (const MouseEvent&)
  43. {
  44. hideTip();
  45. }
  46. void TooltipWindow::updatePosition (const String& tip, Point<int> pos, Rectangle<int> parentArea)
  47. {
  48. setBounds (getLookAndFeel().getTooltipBounds (tip, pos, parentArea));
  49. setVisible (true);
  50. }
  51. void TooltipWindow::displayTip (Point<int> screenPos, const String& tip)
  52. {
  53. jassert (tip.isNotEmpty());
  54. if (! reentrant)
  55. {
  56. ScopedValueSetter<bool> setter (reentrant, true, false);
  57. if (tipShowing != tip)
  58. {
  59. tipShowing = tip;
  60. repaint();
  61. }
  62. if (auto* parent = getParentComponent())
  63. {
  64. updatePosition (tip, parent->getLocalPoint (nullptr, screenPos),
  65. parent->getLocalBounds());
  66. }
  67. else
  68. {
  69. updatePosition (tip, screenPos, Desktop::getInstance().getDisplays()
  70. .getDisplayContaining (screenPos).userArea);
  71. addToDesktop (ComponentPeer::windowHasDropShadow
  72. | ComponentPeer::windowIsTemporary
  73. | ComponentPeer::windowIgnoresKeyPresses
  74. | ComponentPeer::windowIgnoresMouseClicks);
  75. }
  76. toFront (false);
  77. }
  78. }
  79. String TooltipWindow::getTipFor (Component& c)
  80. {
  81. if (Process::isForegroundProcess()
  82. && ! ModifierKeys::getCurrentModifiers().isAnyMouseButtonDown())
  83. {
  84. if (auto* ttc = dynamic_cast<TooltipClient*> (&c))
  85. if (! c.isCurrentlyBlockedByAnotherModalComponent())
  86. return ttc->getTooltip();
  87. }
  88. return {};
  89. }
  90. void TooltipWindow::hideTip()
  91. {
  92. if (! reentrant)
  93. {
  94. tipShowing.clear();
  95. removeFromDesktop();
  96. setVisible (false);
  97. }
  98. }
  99. void TooltipWindow::timerCallback()
  100. {
  101. auto& desktop = Desktop::getInstance();
  102. auto mouseSource = desktop.getMainMouseSource();
  103. auto now = Time::getApproximateMillisecondCounter();
  104. auto* newComp = mouseSource.isTouch() ? nullptr : mouseSource.getComponentUnderMouse();
  105. auto newTip = newComp != nullptr ? getTipFor (*newComp) : String();
  106. bool tipChanged = (newTip != lastTipUnderMouse || newComp != lastComponentUnderMouse);
  107. lastComponentUnderMouse = newComp;
  108. lastTipUnderMouse = newTip;
  109. auto clickCount = desktop.getMouseButtonClickCounter();
  110. auto wheelCount = desktop.getMouseWheelMoveCounter();
  111. bool mouseWasClicked = (clickCount > mouseClicks || wheelCount > mouseWheelMoves);
  112. mouseClicks = clickCount;
  113. mouseWheelMoves = wheelCount;
  114. auto mousePos = mouseSource.getScreenPosition();
  115. bool mouseMovedQuickly = mousePos.getDistanceFrom (lastMousePos) > 12;
  116. lastMousePos = mousePos;
  117. if (tipChanged || mouseWasClicked || mouseMovedQuickly)
  118. lastCompChangeTime = now;
  119. if (isVisible() || now < lastHideTime + 500)
  120. {
  121. // if a tip is currently visible (or has just disappeared), update to a new one
  122. // immediately if needed..
  123. if (newComp == nullptr || mouseWasClicked || newTip.isEmpty())
  124. {
  125. if (isVisible())
  126. {
  127. lastHideTime = now;
  128. hideTip();
  129. }
  130. }
  131. else if (tipChanged)
  132. {
  133. displayTip (mousePos.roundToInt(), newTip);
  134. }
  135. }
  136. else
  137. {
  138. // if there isn't currently a tip, but one is needed, only let it
  139. // appear after a timeout..
  140. if (newTip.isNotEmpty()
  141. && newTip != tipShowing
  142. && now > lastCompChangeTime + (uint32) millisecondsBeforeTipAppears)
  143. {
  144. displayTip (mousePos.roundToInt(), newTip);
  145. }
  146. }
  147. }