QApplication: send QHoverEvents with correct scenePosition()

The QHoverEvent ctor takes two points: pos and globalPos; pos is then
passed as both the scene and global pos to the QSinglePointEvent ctor,
which calls QMutableEventPoint::setScenePosition() on the persistent
QEventPoint instance and then detaches befeore setting ephemeral state.
Therefore, we must construct QHoverEvent with scene position first, not
local position, so that the right value is persisted; it's better to set
local position after the detach(), whereas it's too late to fix the
persistent point then.

Pick-to: 6.4
Fixes: QTBUG-106918
Change-Id: I45726a9ec05bba2fe0cde6f5fb87c269105caca6
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
This commit is contained in:
Shawn Rutledge 2022-09-23 19:43:18 +02:00
parent 55449a6528
commit c3e2a624fb
2 changed files with 105 additions and 2 deletions

View File

@ -2092,8 +2092,9 @@ void QApplicationPrivate::dispatchEnterLeave(QWidget* enter, QWidget* leave, con
QCoreApplication::sendEvent(w, &enterEvent);
if (w->testAttribute(Qt::WA_Hover) &&
(!QApplication::activePopupWidget() || QApplication::activePopupWidget() == w->window())) {
QHoverEvent he(QEvent::HoverEnter, localPos, QPointF(-1, -1), globalPos,
QHoverEvent he(QEvent::HoverEnter, windowPos, QPointF(-1, -1), globalPos,
QGuiApplication::keyboardModifiers());
QMutableEventPoint::setPosition(he.point(0), localPos);
qApp->d_func()->notify_helper(w, &he);
}
}
@ -2772,7 +2773,8 @@ bool QApplication::notify(QObject *receiver, QEvent *e)
while (w) {
if (w->testAttribute(Qt::WA_Hover) &&
(!QApplication::activePopupWidget() || QApplication::activePopupWidget() == w->window())) {
QHoverEvent he(QEvent::HoverMove, relpos, mouse->globalPosition(), relpos - diff, mouse->modifiers());
QHoverEvent he(QEvent::HoverMove, mouse->scenePosition(), mouse->globalPosition(), relpos - diff, mouse->modifiers());
QMutableEventPoint::setPosition(he.point(0), relpos);
d->notify_helper(w, &he);
}
if (w->isWindow() || w->testAttribute(Qt::WA_NoMousePropagation))

View File

@ -345,6 +345,7 @@ private slots:
void enterLeaveOnWindowShowHide_data();
void enterLeaveOnWindowShowHide();
void taskQTBUG_4055_sendSyntheticEnterLeave();
void hoverPosition();
void underMouse();
void taskQTBUG_27643_enterEvents();
#endif
@ -10390,6 +10391,106 @@ void tst_QWidget::taskQTBUG_4055_sendSyntheticEnterLeave()
QTRY_COMPARE(child.numEnterEvents, 1);
QCOMPARE(child.numMouseMoveEvents, 0);
}
void tst_QWidget::hoverPosition()
{
if (m_platform == QStringLiteral("wayland"))
QSKIP("Wayland: Clients can't set cursor position on wayland.");
class HoverWidget : public QWidget
{
public:
HoverWidget(QWidget *parent = nullptr) : QWidget(parent) {
setMouseTracking(true);
setAttribute(Qt::WA_Hover);
}
bool event(QEvent *ev) override {
switch (ev->type()) {
case QEvent::HoverMove:
// The docs say that WA_Hover will cause a paint event on enter and leave, but not on move.
update();
Q_FALLTHROUGH();
case QEvent::HoverEnter:
case QEvent::HoverLeave: {
qCDebug(lcTests) << ev;
lastHoverType = ev->type();
++hoverEventCount;
QHoverEvent *hov = static_cast<QHoverEvent *>(ev);
mousePos = hov->position().toPoint();
mouseScenePos = hov->scenePosition().toPoint();
if (ev->type() == QEvent::HoverEnter)
mouseEnterScenePos = hov->scenePosition().toPoint();
break;
}
default:
break;
}
return QWidget::event(ev);
}
void paintEvent(QPaintEvent *) override {
++paintEventCount;
QPainter painter(this);
if (mousePos.x() > 0)
painter.setPen(Qt::red);
painter.drawRect(0, 0, width(), height());
painter.setPen(Qt::darkGreen);
painter.drawLine(mousePos - QPoint(crossHalfWidth, 0), mousePos + QPoint(crossHalfWidth, 0));
painter.drawLine(mousePos - QPoint(0, crossHalfWidth), mousePos + QPoint(0, crossHalfWidth));
}
QEvent::Type lastHoverType = QEvent::None;
int hoverEventCount = 0;
int paintEventCount = 0;
QPoint mousePos;
QPoint mouseScenePos;
QPoint mouseEnterScenePos;
private:
const int crossHalfWidth = 5;
};
QCursor::setPos(m_safeCursorPos);
if (!QTest::qWaitFor([this]{ return QCursor::pos() == m_safeCursorPos; }))
QSKIP("Can't move cursor");
QWidget root;
root.resize(300, 300);
HoverWidget h(&root);
h.setGeometry(100, 100, 100, 100);
root.show();
QVERIFY(QTest::qWaitForWindowExposed(&root));
const QPoint middle(50, 50);
QPoint curpos = h.mapToGlobal(middle);
QCursor::setPos(curpos);
if (!QTest::qWaitFor([curpos]{ return QCursor::pos() == curpos; }))
QSKIP("Can't move cursor");
QTRY_COMPARE_GE(h.hoverEventCount, 1); // HoverEnter and then probably HoverMove, so usually 2
QTRY_COMPARE_GE(h.paintEventCount, 2);
const int enterHoverEventCount = h.hoverEventCount;
qCDebug(lcTests) << "hover enter events:" << enterHoverEventCount << "last was" << h.lastHoverType
<< "; paint events:" << h.paintEventCount;
QCOMPARE(h.mousePos, middle);
QCOMPARE(h.mouseEnterScenePos, h.mapToParent(middle));
QCOMPARE(h.mouseScenePos, h.mapToParent(middle));
QCOMPARE(h.lastHoverType, enterHoverEventCount == 1 ? QEvent::HoverEnter : QEvent::HoverMove);
curpos += {10, 10};
QCursor::setPos(curpos);
if (!QTest::qWaitFor([curpos]{ return QCursor::pos() == curpos; }))
QSKIP("Can't move cursor");
QTRY_COMPARE(h.hoverEventCount, enterHoverEventCount + 1);
QCOMPARE(h.lastHoverType, QEvent::HoverMove);
QTRY_COMPARE_GE(h.paintEventCount, 3);
curpos += {50, 50}; // in the outer widget, but leaving the inner widget
QCursor::setPos(curpos);
if (!QTest::qWaitFor([curpos]{ return QCursor::pos() == curpos; }))
QSKIP("Can't move cursor");
QTRY_COMPARE(h.lastHoverType, QEvent::HoverLeave);
QCOMPARE_GE(h.hoverEventCount, enterHoverEventCount + 2);
QTRY_COMPARE_GE(h.paintEventCount, 4);
}
#endif
void tst_QWidget::windowFlags()