Skip to content

Commit 205e31d

Browse files
author
Richard Moe Gustavsen
committed
DA: ignore disabled HoverHandlers when delivering hover events
According to the documentation for HoverHandler::enabled, a disabled hover handler will not accept any mouse events. It therefore follows naturally that a disabled HoverHandler should also not affect event propagation elsewhere. This patch will change the implementation, so that we don't deliver hover events to HoverHandlers that are disabled. This also means that disabled HoverHandlers will no longer block propagation to its siblings. [ChangeLog][QtQuick][HoverHandler] Disabled hover handlers will no longer receive hover events, or block siblings from being hovered. Pick-to: 6.4 6.3 6.2 Fixes: QTBUG-106548 Change-Id: I7f2e459ba39f1e23cdb13bf94f8754e185dcd0c1 Reviewed-by: Shawn Rutledge <[email protected]>
1 parent 34a42da commit 205e31d

File tree

3 files changed

+77
-14
lines changed

3 files changed

+77
-14
lines changed

src/quick/handlers/qquickhoverhandler.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,8 @@ bool QQuickHoverHandler::event(QEvent *event)
8282
void QQuickHoverHandler::componentComplete()
8383
{
8484
QQuickSinglePointHandler::componentComplete();
85-
if (auto par = parentItem()) {
86-
par->setAcceptHoverEvents(true);
85+
if (auto par = parentItem())
8786
QQuickItemPrivate::get(par)->setHasHoverInChild(true);
88-
}
8987
}
9088

9189
bool QQuickHoverHandler::wantsPointerEvent(QPointerEvent *event)

src/quick/util/qquickdeliveryagent.cpp

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1017,10 +1017,11 @@ bool QQuickDeliveryAgentPrivate::deliverHoverEvent(
10171017
accepted, recursion stops. Returns \c true in that case, or \c false if the
10181018
event is rejected.
10191019
1020-
All items that have hover enabled (either explicitly, from
1021-
setAcceptHoverEvents(), or implicitly by having HoverHandlers) will have
1022-
the QQuickItemPrivate::hoverEnabled flag set. And all their anchestors will
1023-
have the QQuickItemPrivate::subtreeHoverEnabledset. This function will
1020+
Each item that has hover enabled (from setAcceptHoverEvents()) has the
1021+
QQuickItemPrivate::hoverEnabled flag set. This only controls whether we
1022+
should send hover events to the item itself. (HoverHandlers no longer set
1023+
this flag.) When an item has hoverEnabled set, all its ancestors have the
1024+
QQuickItemPrivate::subtreeHoverEnabled set. This function will
10241025
follow the subtrees that have subtreeHoverEnabled by recursing into each
10251026
child with that flag set. And for each child (in addition to the item
10261027
itself) that also has hoverEnabled set, we call deliverHoverEventToItem()
@@ -1032,10 +1033,19 @@ bool QQuickDeliveryAgentPrivate::deliverHoverEvent(
10321033
of an item that is stacked underneath, will not. Note that since siblings
10331034
can overlap, there can be more than one leaf item under the mouse.
10341035
1036+
Note that HoverHandler doesn't set the hoverEnabled flag on the parent item.
1037+
But still, adding a HoverHandler to an item will set its subtreeHoverEnabled flag.
1038+
So all the propagation logic described above will otherwise be the same.
1039+
But the hoverEnabled flag can be used to resolve if subtreeHoverEnabled is on
1040+
because the application explicitly requested it (setAcceptHoverEvents()), or
1041+
indirectly, because the item has HoverHandlers.
1042+
10351043
For legacy reasons (Qt 6.1), as soon as we find a leaf item that has hover
10361044
enabled, and therefore receives the event, we stop recursing into the remaining
10371045
siblings (even if the event was ignored). This means that we only allow hover
10381046
events to propagate up the direct parent-child hierarchy, and not to siblings.
1047+
However, if the first candidate HoverHandler is disabled, delivery continues
1048+
to the next one, which may be a sibling (QTBUG-106548).
10391049
*/
10401050
bool QQuickDeliveryAgentPrivate::deliverHoverEventRecursive(
10411051
QQuickItem *item, const QPointF &scenePos, const QPointF &lastScenePos,
@@ -1073,8 +1083,7 @@ bool QQuickDeliveryAgentPrivate::deliverHoverEventRecursive(
10731083

10741084
// All decendants have been visited.
10751085
// Now deliver the event to the item
1076-
if (itemPrivate->hoverEnabled)
1077-
return deliverHoverEventToItem(item, scenePos, lastScenePos, modifiers, timestamp, false);
1086+
return deliverHoverEventToItem(item, scenePos, lastScenePos, modifiers, timestamp, false);
10781087

10791088
// Continue propagation / recursion
10801089
return false;
@@ -1101,14 +1110,17 @@ bool QQuickDeliveryAgentPrivate::deliverHoverEventToItem(
11011110
qCDebug(lcHoverTrace) << "item:" << item << "scene pos:" << scenePos << "localPos:" << localPos
11021111
<< "wasHovering:" << wasHovering << "isHovering:" << isHovering;
11031112

1104-
if (isHovering)
1105-
hoveredLeafItemFound = true;
1106-
1107-
// Send enter/move/leave event to the item
11081113
bool accepted = false;
1109-
if (isHovering && !clearHover) {
1114+
1115+
// Start by sending out enter/move/leave events to the item.
1116+
// Note that hoverEnabled only controls if we should send out hover events to the
1117+
// item itself. HoverHandlers are not included, and are dealt with separately below.
1118+
if (itemPrivate->hoverEnabled && isHovering && !clearHover) {
11101119
// Add the item to the list of hovered items (if it doesn't exist there
11111120
// from before), and update hoverId to mark that it's (still) hovered.
1121+
// Also set hoveredLeafItemFound, so that only propagate in a straight
1122+
// line towards the root from now on.
1123+
hoveredLeafItemFound = true;
11121124
hoverItems[item] = currentHoverId;
11131125
if (wasHovering)
11141126
accepted = sendHoverEvent(QEvent::HoverMove, item, scenePos, lastScenePos, modifiers, timestamp);
@@ -1123,6 +1135,7 @@ bool QQuickDeliveryAgentPrivate::deliverHoverEventToItem(
11231135
if (!itemPrivate->hasPointerHandlers())
11241136
return accepted;
11251137

1138+
// Next, send out hover events to the hover handlers.
11261139
// If the item didn't accept the hover event, 'accepted' is now false.
11271140
// Otherwise it's true, and then it should stay the way regardless of
11281141
// whether or not the hoverhandlers themselves are hovered.
@@ -1136,6 +1149,8 @@ bool QQuickDeliveryAgentPrivate::deliverHoverEventToItem(
11361149

11371150
for (QQuickPointerHandler *h : itemPrivate->extra->pointerHandlers) {
11381151
if (QQuickHoverHandler *hh = qmlobject_cast<QQuickHoverHandler *>(h)) {
1152+
if (!hh->isHovered())
1153+
continue;
11391154
hoverEvent.setAccepted(true);
11401155
QCoreApplication::sendEvent(hh, &hoverEvent);
11411156
}
@@ -1146,11 +1161,14 @@ bool QQuickDeliveryAgentPrivate::deliverHoverEventToItem(
11461161

11471162
for (QQuickPointerHandler *h : itemPrivate->extra->pointerHandlers) {
11481163
if (QQuickHoverHandler *hh = qmlobject_cast<QQuickHoverHandler *>(h)) {
1164+
if (!hh->enabled())
1165+
continue;
11491166
hoverEvent.setAccepted(true);
11501167
hh->handlePointerEvent(&hoverEvent);
11511168
if (hh->isHovered()) {
11521169
// Mark the whole item as updated, even if only the handler is
11531170
// actually in a hovered state (because of HoverHandler.margins)
1171+
hoveredLeafItemFound = true;
11541172
hoverItems[item] = currentHoverId;
11551173
if (hh->isBlocking()) {
11561174
qCDebug(lcHoverTrace) << "skipping rest of hover delivery due to blocking" << hh;

tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ private slots:
3939
void hoverHandlerAndUnderlyingHoverHandler();
4040
void mouseAreaAndUnderlyingHoverHandler();
4141
void hoverHandlerAndUnderlyingMouseArea();
42+
void disabledHoverHandlerAndUnderlyingMouseArea();
4243
void movingItemWithHoverHandler();
4344
void margin();
4445
void window();
@@ -278,6 +279,52 @@ void tst_HoverHandler::hoverHandlerAndUnderlyingMouseArea()
278279
#endif
279280
}
280281

282+
void tst_HoverHandler::disabledHoverHandlerAndUnderlyingMouseArea()
283+
{
284+
// Check that if a disabled HoverHandler is installed on an item, it
285+
// will not participate in hover event delivery, and as such, also
286+
// not block propagation to siblings.
287+
QScopedPointer<QQuickView> windowPtr;
288+
createView(windowPtr, "lesHoverables.qml");
289+
QQuickView * window = windowPtr.data();
290+
QQuickItem * bottomSidebar = window->rootObject()->findChild<QQuickItem *>("bottomSidebar");
291+
QVERIFY(bottomSidebar);
292+
QQuickMouseArea *bottomSidebarMA = bottomSidebar->findChild<QQuickMouseArea *>("bottomSidebarMA");
293+
QVERIFY(bottomSidebarMA);
294+
QQuickItem * button = bottomSidebar->findChild<QQuickItem *>("buttonWithHH");
295+
QVERIFY(button);
296+
QQuickHoverHandler *buttonHH = button->findChild<QQuickHoverHandler *>("buttonHH");
297+
QVERIFY(buttonHH);
298+
299+
// By disabling the HoverHandler, it should no longer
300+
// block the sibling MouseArea underneath from receiving hover events.
301+
buttonHH->setEnabled(false);
302+
303+
QPoint buttonCenter(button->mapToScene(QPointF(button->width() / 2, button->height() / 2)).toPoint());
304+
QPoint rightOfButton(button->mapToScene(QPointF(button->width() + 2, button->height() / 2)).toPoint());
305+
QPoint outOfSidebar(bottomSidebar->mapToScene(QPointF(bottomSidebar->width() + 2, bottomSidebar->height() / 2)).toPoint());
306+
QSignalSpy sidebarHoveredSpy(bottomSidebarMA, SIGNAL(hoveredChanged()));
307+
QSignalSpy buttonHoveredSpy(buttonHH, SIGNAL(hoveredChanged()));
308+
309+
QTest::mouseMove(window, outOfSidebar);
310+
QCOMPARE(bottomSidebarMA->hovered(), false);
311+
QCOMPARE(sidebarHoveredSpy.count(), 0);
312+
QCOMPARE(buttonHH->isHovered(), false);
313+
QCOMPARE(buttonHoveredSpy.count(), 0);
314+
315+
QTest::mouseMove(window, buttonCenter);
316+
QCOMPARE(bottomSidebarMA->hovered(), true);
317+
QCOMPARE(sidebarHoveredSpy.count(), 1);
318+
QCOMPARE(buttonHH->isHovered(), false);
319+
QCOMPARE(buttonHoveredSpy.count(), 0);
320+
321+
QTest::mouseMove(window, rightOfButton);
322+
QCOMPARE(bottomSidebarMA->hovered(), true);
323+
QCOMPARE(sidebarHoveredSpy.count(), 1);
324+
QCOMPARE(buttonHH->isHovered(), false);
325+
QCOMPARE(buttonHoveredSpy.count(), 0);
326+
}
327+
281328
void tst_HoverHandler::movingItemWithHoverHandler()
282329
{
283330
if (isPlatformWayland())

0 commit comments

Comments
 (0)