paulxstretch/deps/juce/modules/juce_gui_basics/windows/juce_TooltipWindow.cpp

256 lines
7.5 KiB
C++

/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
TooltipWindow::TooltipWindow (Component* parentComp, int delayMs)
: Component ("tooltip"),
millisecondsBeforeTipAppears (delayMs)
{
setAlwaysOnTop (true);
setOpaque (true);
setAccessible (false);
if (parentComp != nullptr)
parentComp->addChildComponent (this);
auto& desktop = Desktop::getInstance();
if (desktop.getMainMouseSource().canHover())
{
desktop.addGlobalMouseListener (this);
startTimer (123);
}
}
TooltipWindow::~TooltipWindow()
{
hideTip();
Desktop::getInstance().removeGlobalMouseListener (this);
}
void TooltipWindow::setMillisecondsBeforeTipAppears (const int newTimeMs) noexcept
{
millisecondsBeforeTipAppears = newTimeMs;
}
void TooltipWindow::paint (Graphics& g)
{
getLookAndFeel().drawTooltip (g, tipShowing, getWidth(), getHeight());
}
void TooltipWindow::mouseEnter (const MouseEvent& e)
{
if (e.eventComponent == this)
hideTip();
}
void TooltipWindow::mouseDown (const MouseEvent&)
{
if (isVisible())
dismissalMouseEventOccurred = true;
}
void TooltipWindow::mouseWheelMove (const MouseEvent&, const MouseWheelDetails&)
{
if (isVisible())
dismissalMouseEventOccurred = true;
}
void TooltipWindow::updatePosition (const String& tip, Point<int> pos, Rectangle<int> parentArea)
{
setBounds (getLookAndFeel().getTooltipBounds (tip, pos, parentArea));
setVisible (true);
}
#if JUCE_DEBUG
static Array<TooltipWindow*> activeTooltipWindows;
#endif
void TooltipWindow::displayTip (Point<int> screenPos, const String& tip)
{
jassert (tip.isNotEmpty());
displayTipInternal (screenPos, tip, ShownManually::yes);
}
void TooltipWindow::displayTipInternal (Point<int> screenPos, const String& tip, ShownManually shownManually)
{
if (! reentrant)
{
ScopedValueSetter<bool> setter (reentrant, true, false);
if (tipShowing != tip)
{
tipShowing = tip;
repaint();
}
if (auto* parent = getParentComponent())
{
updatePosition (tip, parent->getLocalPoint (nullptr, screenPos),
parent->getLocalBounds());
}
else
{
const auto physicalPos = ScalingHelpers::scaledScreenPosToUnscaled (screenPos);
const auto scaledPos = ScalingHelpers::unscaledScreenPosToScaled (*this, physicalPos);
updatePosition (tip, scaledPos, Desktop::getInstance().getDisplays().getDisplayForPoint (screenPos)->userArea);
addToDesktop (ComponentPeer::windowHasDropShadow
| ComponentPeer::windowIsTemporary
| ComponentPeer::windowIgnoresKeyPresses
| ComponentPeer::windowIgnoresMouseClicks);
}
#if JUCE_DEBUG
activeTooltipWindows.addIfNotAlreadyThere (this);
auto* parent = getParentComponent();
for (auto* w : activeTooltipWindows)
{
if (w != nullptr && w != this && w->tipShowing == tipShowing && w->getParentComponent() == parent)
{
// Looks like you have more than one TooltipWindow showing the same tip..
// Be careful not to create more than one instance of this class with the
// same parent component!
jassertfalse;
}
}
#endif
toFront (false);
manuallyShownTip = shownManually == ShownManually::yes ? tip : String();
dismissalMouseEventOccurred = false;
}
}
String TooltipWindow::getTipFor (Component& c)
{
if (isForegroundOrEmbeddedProcess (&c)
&& ! ModifierKeys::currentModifiers.isAnyMouseButtonDown())
{
if (auto* ttc = dynamic_cast<TooltipClient*> (&c))
if (! c.isCurrentlyBlockedByAnotherModalComponent())
return ttc->getTooltip();
}
return {};
}
void TooltipWindow::hideTip()
{
if (isVisible() && ! reentrant)
{
tipShowing = {};
manuallyShownTip = {};
dismissalMouseEventOccurred = false;
removeFromDesktop();
setVisible (false);
lastHideTime = Time::getApproximateMillisecondCounter();
#if JUCE_DEBUG
activeTooltipWindows.removeAllInstancesOf (this);
#endif
}
}
float TooltipWindow::getDesktopScaleFactor() const
{
if (lastComponentUnderMouse != nullptr)
return Component::getApproximateScaleFactorForComponent (lastComponentUnderMouse);
return Component::getDesktopScaleFactor();
}
std::unique_ptr<AccessibilityHandler> TooltipWindow::createAccessibilityHandler()
{
return createIgnoredAccessibilityHandler (*this);
}
void TooltipWindow::timerCallback()
{
const auto mouseSource = Desktop::getInstance().getMainMouseSource();
auto* newComp = mouseSource.isTouch() ? nullptr : mouseSource.getComponentUnderMouse();
if (manuallyShownTip.isNotEmpty())
{
if (dismissalMouseEventOccurred || newComp == nullptr)
hideTip();
return;
}
if (newComp == nullptr || getParentComponent() == nullptr || newComp->getPeer() == getPeer())
{
const auto newTip = newComp != nullptr ? getTipFor (*newComp) : String();
const auto mousePos = mouseSource.getScreenPosition();
const auto mouseMovedQuickly = (mousePos.getDistanceFrom (lastMousePos) > 12);
lastMousePos = mousePos;
const auto tipChanged = (newTip != lastTipUnderMouse || newComp != lastComponentUnderMouse);
const auto now = Time::getApproximateMillisecondCounter();
lastComponentUnderMouse = newComp;
lastTipUnderMouse = newTip;
if (tipChanged || dismissalMouseEventOccurred || mouseMovedQuickly)
lastCompChangeTime = now;
const auto showTip = [this, &mouseSource, &mousePos, &newTip]
{
if (mouseSource.getLastMouseDownPosition() != lastMousePos)
displayTipInternal (mousePos.roundToInt(), newTip, ShownManually::no);
};
if (isVisible() || now < lastHideTime + 500)
{
// if a tip is currently visible (or has just disappeared), update to a new one
// immediately if needed..
if (newComp == nullptr || dismissalMouseEventOccurred || newTip.isEmpty())
hideTip();
else if (tipChanged)
showTip();
}
else
{
// if there isn't currently a tip, but one is needed, only let it appear after a timeout
if (newTip.isNotEmpty()
&& newTip != tipShowing
&& now > lastCompChangeTime + (uint32) millisecondsBeforeTipAppears)
{
showTip();
}
}
}
}
} // namespace juce