aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShawn Rutledge <shawn.rutledge@qt.io>2020-09-23 16:55:10 +0200
committerShawn Rutledge <shawn.rutledge@qt.io>2020-10-07 22:43:16 +0200
commit2acb31641fc9c34d24ac29232cdfec2673834629 (patch)
tree5561b7f3db94f17a5687b486b26363114656a80e
parentb0ad53347fdb26c52c6c0456adf19b049a78ea3d (diff)
QQuickListView: prevent mouse delivery in floating header or footer
Earlier we reimplemented the contains() method of ListView to prevent dragging in an Overlay or Pullback header or footer. But in QQuickWindow (QQuickWindowPrivate::pointerTargets()), an early check prevents delivery of pointer events to an item that is clipped and for which contains() returns false, and also to its children. In that case, the header or footer no longer responds to a mouse event even if you put a MouseArea in it. Reverts 6ad3445f1e159d9beea936b66d267dcaacdc5d6c; reimplemented using similar logic in a new QQuickListViewPrivate::wantsPointerEvent() method, overriding QQuickFlickablePrivate::wantsPointerEvent(), which is now checked in event-handling code in addition to checking the interactive flag. Done-with: Wang Chuan <ouchuanm@outlook.com> Task-number: QTBUG-74046 Fixes: QTBUG-85302 Change-Id: I9474f035d26b74ee36c0ac19e45a77de2e694bf1 Reviewed-by: Wang Chuan <ouchuanm@outlook.com> Reviewed-by: Mitch Curtis <mitch.curtis@qt.io> Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io> (cherry picked from commit 6857ad3e686a5e2b45d28a7f47dca3210608da50) Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
-rw-r--r--src/quick/items/qquickflickable.cpp10
-rw-r--r--src/quick/items/qquickflickable_p_p.h3
-rw-r--r--src/quick/items/qquicklistview.cpp56
-rw-r--r--src/quick/items/qquicklistview_p.h2
-rw-r--r--tests/auto/quick/qquicklistview/data/clickHeaderAndFooterWhenClip.qml60
-rw-r--r--tests/auto/quick/qquicklistview/tst_qquicklistview.cpp27
6 files changed, 142 insertions, 16 deletions
diff --git a/src/quick/items/qquickflickable.cpp b/src/quick/items/qquickflickable.cpp
index f222a4a32e..9a68be4c49 100644
--- a/src/quick/items/qquickflickable.cpp
+++ b/src/quick/items/qquickflickable.cpp
@@ -1424,7 +1424,7 @@ void QQuickFlickablePrivate::handleMouseReleaseEvent(QMouseEvent *event)
void QQuickFlickable::mousePressEvent(QMouseEvent *event)
{
Q_D(QQuickFlickable);
- if (d->interactive) {
+ if (d->interactive && d->wantsPointerEvent(event)) {
if (!d->pressed)
d->handleMousePressEvent(event);
event->accept();
@@ -1436,7 +1436,7 @@ void QQuickFlickable::mousePressEvent(QMouseEvent *event)
void QQuickFlickable::mouseMoveEvent(QMouseEvent *event)
{
Q_D(QQuickFlickable);
- if (d->interactive) {
+ if (d->interactive && d->wantsPointerEvent(event)) {
d->handleMouseMoveEvent(event);
event->accept();
} else {
@@ -1447,7 +1447,7 @@ void QQuickFlickable::mouseMoveEvent(QMouseEvent *event)
void QQuickFlickable::mouseReleaseEvent(QMouseEvent *event)
{
Q_D(QQuickFlickable);
- if (d->interactive) {
+ if (d->interactive && d->wantsPointerEvent(event)) {
if (d->delayedPressEvent) {
d->replayDelayedPress();
@@ -1475,7 +1475,7 @@ void QQuickFlickable::mouseReleaseEvent(QMouseEvent *event)
void QQuickFlickable::wheelEvent(QWheelEvent *event)
{
Q_D(QQuickFlickable);
- if (!d->interactive) {
+ if (!d->interactive || !d->wantsPointerEvent(event)) {
QQuickItem::wheelEvent(event);
return;
}
@@ -2445,7 +2445,7 @@ bool QQuickFlickable::filterMouseEvent(QQuickItem *receiver, QMouseEvent *event)
bool QQuickFlickable::childMouseEventFilter(QQuickItem *i, QEvent *e)
{
Q_D(QQuickFlickable);
- if (!isVisible() || !isEnabled() || !isInteractive()) {
+ if (!isVisible() || !isEnabled() || !isInteractive() || !d->wantsPointerEvent(e)) {
d->cancelInteraction();
return QQuickItem::childMouseEventFilter(i, e);
}
diff --git a/src/quick/items/qquickflickable_p_p.h b/src/quick/items/qquickflickable_p_p.h
index 3f5f11effd..414c9c33d6 100644
--- a/src/quick/items/qquickflickable_p_p.h
+++ b/src/quick/items/qquickflickable_p_p.h
@@ -208,6 +208,9 @@ public:
void addPointerHandler(QQuickPointerHandler *h) override;
+ // TODO Qt 6: QPointerEvent
+ virtual bool wantsPointerEvent(const QEvent *) { return true; }
+
public:
QQuickItem *contentItem;
diff --git a/src/quick/items/qquicklistview.cpp b/src/quick/items/qquicklistview.cpp
index 9dd85ded19..0f9394f695 100644
--- a/src/quick/items/qquicklistview.cpp
+++ b/src/quick/items/qquicklistview.cpp
@@ -58,6 +58,8 @@ QT_BEGIN_NAMESPACE
#define QML_FLICK_SNAPONETHRESHOLD 30
#endif
+Q_LOGGING_CATEGORY(lcEvents, "qt.quick.listview.events")
+
class FxListItemSG;
class QQuickListViewPrivate : public QQuickItemViewPrivate
@@ -143,6 +145,9 @@ public:
void fixupHeader();
void fixupHeaderCompleted();
+
+ bool wantsPointerEvent(const QEvent *event) override;
+
QQuickListView::Orientation orient;
qreal visiblePos;
qreal averageSize;
@@ -179,6 +184,7 @@ public:
bool correctFlick : 1;
bool inFlickCorrection : 1;
+ bool wantedMousePress : 1;
QQuickListViewPrivate()
: orient(QQuickListView::Vertical)
@@ -192,7 +198,7 @@ public:
, sectionCriteria(nullptr), currentSectionItem(nullptr), nextSectionItem(nullptr)
, overshootDist(0.0), desiredViewportPosition(0.0), fixupHeaderPosition(0.0)
, headerNeedsSeparateFixup(false), desiredHeaderVisible(false)
- , correctFlick(false), inFlickCorrection(false)
+ , correctFlick(false), inFlickCorrection(false), wantedMousePress(false)
{
highlightMoveDuration = -1; //override default value set in base class
}
@@ -3819,20 +3825,52 @@ QQuickListViewAttached *QQuickListView::qmlAttachedProperties(QObject *obj)
return new QQuickListViewAttached(obj);
}
-bool QQuickListView::contains(const QPointF &point) const
+/*! \internal
+ Prevents clicking or dragging through floating headers (QTBUG-74046).
+*/
+bool QQuickListViewPrivate::wantsPointerEvent(const QEvent *event)
{
- bool ret = QQuickItemView::contains(point);
- // QTBUG-74046: if a mouse press "falls through" a floating header or footer, don't allow dragging the list from there
- if (ret) {
- if (auto header = headerItem()) {
- if (headerPositioning() != QQuickListView::InlineHeader && header->contains(mapToItem(header, point)))
+ Q_Q(const QQuickListView);
+ bool ret = true;
+
+ QPointF pos;
+ // TODO switch not needed in Qt 6: use points().first().position()
+ switch (event->type()) {
+ case QEvent::Wheel:
+ pos = static_cast<const QWheelEvent *>(event)->position();
+ break;
+ case QEvent::MouseButtonPress:
+ pos = static_cast<const QMouseEvent *>(event)->localPos();
+ break;
+ default:
+ break;
+ }
+
+ if (!pos.isNull()) {
+ if (auto header = q->headerItem()) {
+ if (q->headerPositioning() != QQuickListView::InlineHeader &&
+ header->contains(q->mapToItem(header, pos)))
ret = false;
}
- if (auto footer = footerItem()) {
- if (footerPositioning() != QQuickListView::InlineFooter && footer->contains(mapToItem(footer, point)))
+ if (auto footer = q->footerItem()) {
+ if (q->footerPositioning() != QQuickListView::InlineFooter &&
+ footer->contains(q->mapToItem(footer, pos)))
ret = false;
}
}
+
+ switch (event->type()) {
+ case QEvent::MouseButtonPress:
+ wantedMousePress = ret;
+ break;
+ case QEvent::MouseMove:
+ ret = wantedMousePress;
+ break;
+ default:
+ break;
+ }
+
+ qCDebug(lcEvents) << q << (ret ? "WANTS" : "DOESN'T want") << event;
return ret;
}
diff --git a/src/quick/items/qquicklistview_p.h b/src/quick/items/qquicklistview_p.h
index be21b93155..1c72a10190 100644
--- a/src/quick/items/qquicklistview_p.h
+++ b/src/quick/items/qquicklistview_p.h
@@ -179,8 +179,6 @@ public:
static QQuickListViewAttached *qmlAttachedProperties(QObject *);
- bool contains(const QPointF &point) const override;
-
public Q_SLOTS:
void incrementCurrentIndex();
void decrementCurrentIndex();
diff --git a/tests/auto/quick/qquicklistview/data/clickHeaderAndFooterWhenClip.qml b/tests/auto/quick/qquicklistview/data/clickHeaderAndFooterWhenClip.qml
new file mode 100644
index 0000000000..c48e2d47d1
--- /dev/null
+++ b/tests/auto/quick/qquicklistview/data/clickHeaderAndFooterWhenClip.qml
@@ -0,0 +1,60 @@
+import QtQuick 2.9
+import QtQuick.Window 2.15
+
+ListView {
+ id: list
+ anchors.fill: parent
+ property bool headerPressed: false
+ property bool footerPressed: false
+ model: ListModel {
+ ListElement {
+ name: "Element 1"
+ }
+ ListElement {
+ name: "Element 2"
+ }
+ ListElement {
+ name: "Element 3"
+ }
+ ListElement {
+ name: "Element 4"
+ }
+ ListElement {
+ name: "Element 5"
+ }
+ ListElement {
+ name: "Element 6"
+ }
+ }
+ clip: true
+ headerPositioning: ListView.OverlayHeader
+ footerPositioning: ListView.OverlayHeader
+
+ delegate: Text {
+ height: 100
+ text: name
+ }
+
+ header: Rectangle {
+ width: parent.width
+ height: 50
+ z: 2
+ color: "blue"
+ MouseArea {
+ objectName: "header"
+ anchors.fill: parent
+ onClicked: list.headerPressed = true
+ }
+ }
+ footer: Rectangle {
+ width: parent.width
+ height: 50
+ color: "red"
+ z: 2
+ MouseArea {
+ objectName: "footer"
+ anchors.fill: parent
+ onClicked: list.footerPressed = true
+ }
+ }
+}
diff --git a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp
index f63793ca80..2af6b084fb 100644
--- a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp
+++ b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp
@@ -297,6 +297,7 @@ private slots:
void objectModelCulling();
void requiredObjectListModel();
+ void clickHeaderAndFooterWhenClip();
private:
template <class T> void items(const QUrl &source);
@@ -10067,6 +10068,32 @@ void tst_QQuickListView::requiredObjectListModel()
}
}
+void tst_QQuickListView::clickHeaderAndFooterWhenClip() // QTBUG-85302
+{
+ QScopedPointer<QQuickView> window(createView());
+ window->setSource(testFileUrl("clickHeaderAndFooterWhenClip.qml"));
+ window->resize(640, 480);
+ window->show();
+
+ QVERIFY(QTest::qWaitForWindowExposed(window.data()));
+ auto *root = window->rootObject();
+ QVERIFY(root);
+
+ auto *header = root->findChild<QQuickItem *>("header");
+ QVERIFY(header);
+
+ auto *footer = root->findChild<QQuickItem *>("footer");
+ QVERIFY(footer);
+
+ QVERIFY(root->property("headerPressed").isValid() && root->property("headerPressed").canConvert<bool>() && !root->property("headerPressed").toBool());
+ QTest::mouseClick(window.data(), Qt::LeftButton, Qt::NoModifier, header->mapToItem(root, QPoint(header->width() / 2, header->height() / 2)).toPoint());
+ QVERIFY(root->property("headerPressed").toBool());
+
+ QVERIFY(root->property("footerPressed").isValid() && root->property("footerPressed").canConvert<bool>() && !root->property("footerPressed").toBool());
+ QTest::mouseClick(window.data(), Qt::LeftButton, Qt::NoModifier, footer->mapToItem(root, QPoint(footer->width() / 2, footer->height() / 2)).toPoint());
+ QVERIFY(root->property("footerPressed").toBool());
+}
+
QTEST_MAIN(tst_QQuickListView)
#include "tst_qquicklistview.moc"