summaryrefslogtreecommitdiff
path: root/Source
diff options
context:
space:
mode:
authorDentomologist <dentomologist@gmail.com>2023-11-26 13:26:52 -0800
committerDentomologist <dentomologist@gmail.com>2025-08-01 12:30:05 -0700
commit7b52555a5fa2c4223bc9db7a8a91c06c81d7adbf (patch)
tree59416107d4ce05cbdedf41b57234f9d062bc7cd5 /Source
parent36aa7b6f3f721ec34f15e21a9e7c6256d2ba22f9 (diff)
BalloonTip: Don't hide when BalloonTip blocks the cursor
Keep the BalloonTip open when the BalloonTip's arrow prevents the cursor from being inside the spawning ToolTipWidget, which triggers the ToolTipWidget's leaveEvent and would previously close the BalloonTip. When that happens track the cursor until it either leaves the ToolTipWidget's bounding box or leaves the BalloonTip and goes back to the ToolTipWidget, and respectively close the BalloonTip or leave it open.
Diffstat (limited to 'Source')
-rw-r--r--Source/Core/DolphinQt/Config/ToolTipControls/BalloonTip.cpp65
-rw-r--r--Source/Core/DolphinQt/Config/ToolTipControls/BalloonTip.h16
-rw-r--r--Source/Core/DolphinQt/Config/ToolTipControls/ToolTipWidget.h41
3 files changed, 108 insertions, 14 deletions
diff --git a/Source/Core/DolphinQt/Config/ToolTipControls/BalloonTip.cpp b/Source/Core/DolphinQt/Config/ToolTipControls/BalloonTip.cpp
index 52033a9445..ffed038c19 100644
--- a/Source/Core/DolphinQt/Config/ToolTipControls/BalloonTip.cpp
+++ b/Source/Core/DolphinQt/Config/ToolTipControls/BalloonTip.cpp
@@ -5,11 +5,11 @@
#include <memory>
+#include <QApplication>
#include <QBitmap>
#include <QBrush>
#include <QCursor>
#include <QFont>
-#include <QGuiApplication>
#include <QLabel>
#include <QPainter>
#include <QPainterPath>
@@ -25,11 +25,17 @@
#include <QToolTip>
#endif
+#include "DolphinQt/QtUtils/QueueOnObject.h"
#include "DolphinQt/Settings.h"
namespace
{
std::unique_ptr<BalloonTip> s_the_balloon_tip = nullptr;
+// Remember the parent ToolTipWidget so cursor-related events can see whether the cursor is inside
+// the parent's bounding box or not. Use this variable instead of BalloonTip's parent() member
+// because the ToolTipWidget isn't responsible for deleting the BalloonTip and so doesn't set its
+// parent member.
+QWidget* s_parent = nullptr;
} // namespace
void BalloonTip::ShowBalloon(const QString& title, const QString& message,
@@ -53,6 +59,7 @@ void BalloonTip::ShowBalloon(const QString& title, const QString& message,
void BalloonTip::HideBalloon()
{
+ s_parent = nullptr;
#if defined(__APPLE__)
QToolTip::hideText();
#else
@@ -66,6 +73,9 @@ void BalloonTip::HideBalloon()
BalloonTip::BalloonTip(PrivateTag, const QString& title, QString message, QWidget* const parent)
: QWidget(nullptr, Qt::ToolTip)
{
+ s_parent = parent;
+ setMouseTracking(true);
+
QColor window_color;
QColor text_color;
QColor dolphin_emphasis;
@@ -113,10 +123,61 @@ BalloonTip::BalloonTip(PrivateTag, const QString& title, QString message, QWidge
create_label(message);
}
-void BalloonTip::paintEvent(QPaintEvent*)
+bool BalloonTip::IsCursorInsideWidgetBoundingBox(const QWidget& widget)
+{
+ const QPoint local_cursor_position = widget.mapFromGlobal(QCursor::pos());
+ return widget.rect().contains(local_cursor_position);
+}
+
+bool BalloonTip::IsCursorOnBalloonTip()
+{
+ return s_the_balloon_tip != nullptr &&
+ QApplication::widgetAt(QCursor::pos()) == s_the_balloon_tip.get();
+}
+
+bool BalloonTip::IsWidgetBalloonTipActive(const QWidget& widget)
+{
+ return &widget == s_parent;
+}
+
+// Hiding the balloon causes the BalloonTip widget to be deleted. Triggering that deletion while
+// inside a BalloonTip event handler leads to a use-after-free crash or worse, so queue the deletion
+// for later.
+static void QueueHideBalloon()
+{
+ QueueOnObject(s_parent, BalloonTip::HideBalloon);
+}
+
+void BalloonTip::enterEvent(QEnterEvent* const event)
+{
+ if (!IsCursorInsideWidgetBoundingBox(*s_parent))
+ QueueHideBalloon();
+
+ QWidget::enterEvent(event);
+}
+
+void BalloonTip::mouseMoveEvent(QMouseEvent* const event)
+{
+ if (!IsCursorInsideWidgetBoundingBox(*s_parent))
+ QueueHideBalloon();
+
+ QWidget::mouseMoveEvent(event);
+}
+
+void BalloonTip::leaveEvent(QEvent* const event)
+{
+ if (QApplication::widgetAt(QCursor::pos()) != s_parent)
+ QueueHideBalloon();
+
+ QWidget::leaveEvent(event);
+}
+
+void BalloonTip::paintEvent(QPaintEvent* const event)
{
QPainter painter(this);
painter.drawPixmap(rect(), m_pixmap);
+
+ QWidget::paintEvent(event);
}
void BalloonTip::UpdateBoundsAndRedraw(const QPoint& target_arrow_tip_position,
diff --git a/Source/Core/DolphinQt/Config/ToolTipControls/BalloonTip.h b/Source/Core/DolphinQt/Config/ToolTipControls/BalloonTip.h
index c5df392df9..ca62c1cfea 100644
--- a/Source/Core/DolphinQt/Config/ToolTipControls/BalloonTip.h
+++ b/Source/Core/DolphinQt/Config/ToolTipControls/BalloonTip.h
@@ -7,6 +7,9 @@
#include <QPixmap>
#include <QWidget>
+class QEnterEvent;
+class QEvent;
+class QMouseEvent;
class QPaintEvent;
class QPoint;
class QString;
@@ -29,17 +32,22 @@ public:
const QPoint& target_arrow_tip_position, QWidget* parent,
ShowArrow show_arrow = ShowArrow::Yes, int border_width = 1);
static void HideBalloon();
+ static bool IsCursorInsideWidgetBoundingBox(const QWidget& widget);
+ static bool IsCursorOnBalloonTip();
+ static bool IsWidgetBalloonTipActive(const QWidget& widget);
BalloonTip(PrivateTag, const QString& title, QString message, QWidget* parent);
+protected:
+ void enterEvent(QEnterEvent* event) override;
+ void leaveEvent(QEvent* event) override;
+ void mouseMoveEvent(QMouseEvent* event) override;
+ void paintEvent(QPaintEvent* event) override;
+
private:
void UpdateBoundsAndRedraw(const QPoint& target_arrow_tip_position, ShowArrow show_arrow,
int border_width);
-protected:
- void paintEvent(QPaintEvent*) override;
-
-private:
QColor m_border_color;
QPixmap m_pixmap;
};
diff --git a/Source/Core/DolphinQt/Config/ToolTipControls/ToolTipWidget.h b/Source/Core/DolphinQt/Config/ToolTipControls/ToolTipWidget.h
index 727ae11462..94cd88b87e 100644
--- a/Source/Core/DolphinQt/Config/ToolTipControls/ToolTipWidget.h
+++ b/Source/Core/DolphinQt/Config/ToolTipControls/ToolTipWidget.h
@@ -9,6 +9,11 @@
#include "DolphinQt/Config/ToolTipControls/BalloonTip.h"
+class QEnterEvent;
+class QEvent;
+class QHideEvent;
+class QTimerEvent;
+
constexpr int TOOLTIP_DELAY = 300;
template <class Derived>
@@ -22,28 +27,48 @@ public:
void SetDescription(QString description) { m_description = std::move(description); }
private:
- void enterEvent(QEnterEvent* event) override
+ void enterEvent(QEnterEvent* const event) override
{
- if (m_timer_id)
- return;
- m_timer_id = this->startTimer(TOOLTIP_DELAY);
+ // If the timer is already running, or the cursor is reentering the ToolTipWidget after having
+ // hovered over the BalloonTip, don't start a new timer.
+ if (!m_timer_id && !BalloonTip::IsWidgetBalloonTipActive(*this))
+ m_timer_id = this->startTimer(TOOLTIP_DELAY);
+
+ Derived::enterEvent(event);
}
- void leaveEvent(QEvent* event) override { KillAndHide(); }
- void hideEvent(QHideEvent* event) override { KillAndHide(); }
+ void leaveEvent(QEvent* const event) override
+ {
+ // If the cursor would still be inside the ToolTipWidget but the BalloonTip is covering that
+ // part of it, keep the BalloonTip open. In that case the BalloonTip will then track the cursor
+ // and close itself if it leaves the bounding box of this ToolTipWidget.
+ if (!BalloonTip::IsCursorInsideWidgetBoundingBox(*this) || !BalloonTip::IsCursorOnBalloonTip())
+ KillTimerAndHideBalloon();
+
+ Derived::leaveEvent(event);
+ }
- void timerEvent(QTimerEvent* event) override
+ void hideEvent(QHideEvent* const event) override
+ {
+ KillTimerAndHideBalloon();
+
+ Derived::hideEvent(event);
+ }
+
+ void timerEvent(QTimerEvent* const event) override
{
this->killTimer(*m_timer_id);
m_timer_id.reset();
BalloonTip::ShowBalloon(m_title, m_description,
this->parentWidget()->mapToGlobal(GetToolTipPosition()), this);
+
+ Derived::timerEvent(event);
}
virtual QPoint GetToolTipPosition() const = 0;
- void KillAndHide()
+ void KillTimerAndHideBalloon()
{
if (m_timer_id)
{