summaryrefslogtreecommitdiff
path: root/Source
diff options
context:
space:
mode:
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)
{