| 1 | /**************************************************************************** |
| 2 | ** |
| 3 | ** Copyright (C) 2018 The Qt Company Ltd. |
| 4 | ** Contact: https://www.qt.io/licensing/ |
| 5 | ** |
| 6 | ** This file is part of the QtQml module of the Qt Toolkit. |
| 7 | ** |
| 8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| 9 | ** Commercial License Usage |
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in |
| 11 | ** accordance with the commercial license agreement provided with the |
| 12 | ** Software or, alternatively, in accordance with the terms contained in |
| 13 | ** a written agreement between you and The Qt Company. For licensing terms |
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
| 15 | ** information use the contact form at https://www.qt.io/contact-us. |
| 16 | ** |
| 17 | ** GNU General Public License Usage |
| 18 | ** Alternatively, this file may be used under the terms of the GNU |
| 19 | ** General Public License version 3 as published by the Free Software |
| 20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| 21 | ** included in the packaging of this file. Please review the following |
| 22 | ** information to ensure the GNU General Public License requirements will |
| 23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| 24 | ** |
| 25 | ** $QT_END_LICENSE$ |
| 26 | ** |
| 27 | ****************************************************************************/ |
| 28 | |
| 29 | #include <QtTest/QtTest> |
| 30 | |
| 31 | #include <QtQml/qqmlengine.h> |
| 32 | #include <QtQml/qqmlproperty.h> |
| 33 | #include <QtQuick/private/qquickdraghandler_p.h> |
| 34 | #include <QtQuick/private/qquickrepeater_p.h> |
| 35 | #include <QtQuick/private/qquicktaphandler_p.h> |
| 36 | #include <QtQuick/qquickitem.h> |
| 37 | #include <QtQuick/qquickview.h> |
| 38 | |
| 39 | #include "../../../shared/util.h" |
| 40 | #include "../../shared/viewtestutil.h" |
| 41 | |
| 42 | Q_LOGGING_CATEGORY(lcPointerTests, "qt.quick.pointer.tests" ) |
| 43 | |
| 44 | class tst_DragHandler : public QQmlDataTest |
| 45 | { |
| 46 | Q_OBJECT |
| 47 | public: |
| 48 | tst_DragHandler() |
| 49 | :touchDevice(QTest::createTouchDevice()) |
| 50 | {} |
| 51 | |
| 52 | private slots: |
| 53 | void initTestCase(); |
| 54 | |
| 55 | void defaultPropertyValues(); |
| 56 | void touchDrag_data(); |
| 57 | void touchDrag(); |
| 58 | void mouseDrag_data(); |
| 59 | void mouseDrag(); |
| 60 | void mouseDragThreshold_data(); |
| 61 | void mouseDragThreshold(); |
| 62 | void dragFromMargin(); |
| 63 | void snapMode_data(); |
| 64 | void snapMode(); |
| 65 | void touchDragMulti(); |
| 66 | void touchDragMultiSliders_data(); |
| 67 | void touchDragMultiSliders(); |
| 68 | void touchPassiveGrabbers_data(); |
| 69 | void touchPassiveGrabbers(); |
| 70 | void touchPinchAndMouseMove(); |
| 71 | void underModalLayer(); |
| 72 | |
| 73 | private: |
| 74 | void createView(QScopedPointer<QQuickView> &window, const char *fileName); |
| 75 | QSet<QQuickPointerHandler *> passiveGrabbers(QQuickWindow *window, int pointId = 0); |
| 76 | QTouchDevice *touchDevice; |
| 77 | }; |
| 78 | |
| 79 | void tst_DragHandler::createView(QScopedPointer<QQuickView> &window, const char *fileName) |
| 80 | { |
| 81 | window.reset(other: new QQuickView); |
| 82 | window->setSource(testFileUrl(fileName)); |
| 83 | QTRY_COMPARE(window->status(), QQuickView::Ready); |
| 84 | QQuickViewTestUtil::centerOnScreen(window: window.data()); |
| 85 | QQuickViewTestUtil::moveMouseAway(window: window.data()); |
| 86 | |
| 87 | window->show(); |
| 88 | QVERIFY(QTest::qWaitForWindowActive(window.data())); |
| 89 | QVERIFY(window->rootObject() != nullptr); |
| 90 | } |
| 91 | |
| 92 | QSet<QQuickPointerHandler*> tst_DragHandler::passiveGrabbers(QQuickWindow *window, int pointId /*= 0*/) |
| 93 | { |
| 94 | QSet<QQuickPointerHandler*> result; |
| 95 | QQuickWindowPrivate *winp = QQuickWindowPrivate::get(c: window); |
| 96 | if (QQuickPointerDevice* device = QQuickPointerDevice::touchDevice(d: touchDevice)) { |
| 97 | QQuickPointerEvent *pointerEvent = winp->pointerEventInstance(device); |
| 98 | for (int i = 0; i < pointerEvent->pointCount(); ++i) { |
| 99 | QQuickEventPoint *eventPoint = pointerEvent->point(i); |
| 100 | QVector<QPointer <QQuickPointerHandler> > passives = eventPoint->passiveGrabbers(); |
| 101 | if (!pointId || eventPoint->pointId() == pointId) { |
| 102 | for (auto it = passives.constBegin(); it != passives.constEnd(); ++it) |
| 103 | result << it->data(); |
| 104 | } |
| 105 | } |
| 106 | } |
| 107 | return result; |
| 108 | } |
| 109 | |
| 110 | void tst_DragHandler::initTestCase() |
| 111 | { |
| 112 | // This test assumes that we don't get synthesized mouse events from QGuiApplication |
| 113 | qApp->setAttribute(attribute: Qt::AA_SynthesizeMouseForUnhandledTouchEvents, on: false); |
| 114 | |
| 115 | QQmlDataTest::initTestCase(); |
| 116 | } |
| 117 | |
| 118 | void tst_DragHandler::defaultPropertyValues() |
| 119 | { |
| 120 | QScopedPointer<QQuickView> windowPtr; |
| 121 | createView(window&: windowPtr, fileName: "draggables.qml" ); |
| 122 | QQuickView * window = windowPtr.data(); |
| 123 | |
| 124 | QQuickItem *ball = window->rootObject()->childItems().first(); |
| 125 | QVERIFY(ball); |
| 126 | QQuickDragHandler *dragHandler = ball->findChild<QQuickDragHandler*>(); |
| 127 | QVERIFY(dragHandler); |
| 128 | |
| 129 | QCOMPARE(dragHandler->acceptedButtons(), Qt::LeftButton); |
| 130 | QCOMPARE(dragHandler->translation(), QVector2D()); |
| 131 | QCOMPARE(dragHandler->centroid().position(), QPointF()); |
| 132 | QCOMPARE(dragHandler->centroid().scenePosition(), QPointF()); |
| 133 | QCOMPARE(dragHandler->centroid().pressPosition(), QPointF()); |
| 134 | QCOMPARE(dragHandler->centroid().scenePressPosition(), QPointF()); |
| 135 | QCOMPARE(dragHandler->centroid().sceneGrabPosition(), QPointF()); |
| 136 | } |
| 137 | |
| 138 | void tst_DragHandler::touchDrag_data() |
| 139 | { |
| 140 | QTest::addColumn<int>(name: "dragThreshold" ); |
| 141 | QTest::newRow(dataTag: "threshold zero" ) << 0; |
| 142 | QTest::newRow(dataTag: "threshold one" ) << 1; |
| 143 | QTest::newRow(dataTag: "threshold 20" ) << 20; |
| 144 | QTest::newRow(dataTag: "threshold default" ) << -1; |
| 145 | } |
| 146 | |
| 147 | void tst_DragHandler::touchDrag() |
| 148 | { |
| 149 | QFETCH(int, dragThreshold); |
| 150 | QScopedPointer<QQuickView> windowPtr; |
| 151 | createView(window&: windowPtr, fileName: "draggables.qml" ); |
| 152 | QQuickView * window = windowPtr.data(); |
| 153 | |
| 154 | QQuickItem *ball = window->rootObject()->childItems().first(); |
| 155 | QVERIFY(ball); |
| 156 | QQuickDragHandler *dragHandler = ball->findChild<QQuickDragHandler*>(); |
| 157 | QVERIFY(dragHandler); |
| 158 | if (dragThreshold < 0) { |
| 159 | dragThreshold = QGuiApplication::styleHints()->startDragDistance(); |
| 160 | QCOMPARE(dragHandler->dragThreshold(), dragThreshold); |
| 161 | } else { |
| 162 | dragHandler->setDragThreshold(dragThreshold); |
| 163 | } |
| 164 | |
| 165 | QSignalSpy translationChangedSpy(dragHandler, SIGNAL(translationChanged())); |
| 166 | QSignalSpy centroidChangedSpy(dragHandler, SIGNAL(centroidChanged())); |
| 167 | |
| 168 | QPointF ballCenter = ball->clipRect().center(); |
| 169 | QPointF scenePressPos = ball->mapToScene(point: ballCenter); |
| 170 | QPoint p1 = scenePressPos.toPoint(); |
| 171 | QTest::touchEvent(window, device: touchDevice).press(touchId: 1, pt: p1, window); |
| 172 | QQuickTouchUtils::flush(window); |
| 173 | QVERIFY(!dragHandler->active()); |
| 174 | QCOMPARE(dragHandler->centroid().position(), ballCenter); |
| 175 | QCOMPARE(dragHandler->centroid().pressPosition(), ballCenter); |
| 176 | QCOMPARE(dragHandler->centroid().scenePosition(), scenePressPos); |
| 177 | QCOMPARE(dragHandler->centroid().scenePressPosition(), scenePressPos); |
| 178 | QCOMPARE(dragHandler->centroid().velocity(), QVector2D()); |
| 179 | QCOMPARE(centroidChangedSpy.count(), 1); |
| 180 | p1 += QPoint(dragThreshold, 0); |
| 181 | QTest::touchEvent(window, device: touchDevice).move(touchId: 1, pt: p1, window); |
| 182 | QQuickTouchUtils::flush(window); |
| 183 | qCDebug(lcPointerTests) << "velocity after drag" << dragHandler->centroid().velocity(); |
| 184 | if (dragThreshold > 0) |
| 185 | QTRY_VERIFY(!qFuzzyIsNull(dragHandler->centroid().velocity().x())); |
| 186 | QCOMPARE(centroidChangedSpy.count(), 2); |
| 187 | QVERIFY(!dragHandler->active()); |
| 188 | p1 += QPoint(1, 0); |
| 189 | QTest::touchEvent(window, device: touchDevice).move(touchId: 1, pt: p1, window); |
| 190 | QQuickTouchUtils::flush(window); |
| 191 | QTRY_VERIFY(dragHandler->active()); |
| 192 | QCOMPARE(translationChangedSpy.count(), 0); |
| 193 | QCOMPARE(centroidChangedSpy.count(), 3); |
| 194 | QCOMPARE(dragHandler->translation().x(), 0.0); |
| 195 | QPointF sceneGrabPos = p1; |
| 196 | QCOMPARE(dragHandler->centroid().sceneGrabPosition(), sceneGrabPos); |
| 197 | p1 += QPoint(19, 0); |
| 198 | QTest::touchEvent(window, device: touchDevice).move(touchId: 1, pt: p1, window); |
| 199 | QQuickTouchUtils::flush(window); |
| 200 | QTRY_VERIFY(dragHandler->active()); |
| 201 | QCOMPARE(dragHandler->centroid().position(), ballCenter); |
| 202 | QCOMPARE(dragHandler->centroid().pressPosition(), ballCenter); |
| 203 | QCOMPARE(dragHandler->centroid().scenePosition(), ball->mapToScene(ballCenter)); |
| 204 | QCOMPARE(dragHandler->centroid().scenePressPosition(), scenePressPos); |
| 205 | QCOMPARE(dragHandler->centroid().sceneGrabPosition(), sceneGrabPos); |
| 206 | QCOMPARE(dragHandler->translation().x(), dragThreshold + 20.0); |
| 207 | QCOMPARE(dragHandler->translation().y(), 0.0); |
| 208 | QVERIFY(dragHandler->centroid().velocity().x() > 0); |
| 209 | QCOMPARE(centroidChangedSpy.count(), 4); |
| 210 | QTest::touchEvent(window, device: touchDevice).release(touchId: 1, pt: p1, window); |
| 211 | QQuickTouchUtils::flush(window); |
| 212 | QTRY_VERIFY(!dragHandler->active()); |
| 213 | QCOMPARE(dragHandler->centroid().pressedButtons(), Qt::NoButton); |
| 214 | QCOMPARE(dragHandler->centroid().velocity(), QVector2D()); |
| 215 | QCOMPARE(ball->mapToScene(ballCenter).toPoint(), p1); |
| 216 | QCOMPARE(translationChangedSpy.count(), 1); |
| 217 | QCOMPARE(centroidChangedSpy.count(), 5); |
| 218 | } |
| 219 | |
| 220 | void tst_DragHandler::mouseDrag_data() |
| 221 | { |
| 222 | QTest::addColumn<Qt::MouseButtons>(name: "acceptedButtons" ); |
| 223 | QTest::addColumn<Qt::MouseButtons>(name: "dragButton" ); |
| 224 | QTest::newRow(dataTag: "left: drag" ) << Qt::MouseButtons(Qt::LeftButton) << Qt::MouseButtons(Qt::LeftButton); |
| 225 | QTest::newRow(dataTag: "right: don't drag" ) << Qt::MouseButtons(Qt::LeftButton) << Qt::MouseButtons(Qt::RightButton); |
| 226 | QTest::newRow(dataTag: "left: don't drag" ) << Qt::MouseButtons(Qt::RightButton | Qt::MiddleButton) << Qt::MouseButtons(Qt::LeftButton); |
| 227 | QTest::newRow(dataTag: "right or middle: drag" ) << Qt::MouseButtons(Qt::RightButton | Qt::MiddleButton) << Qt::MouseButtons(Qt::MiddleButton); |
| 228 | } |
| 229 | |
| 230 | void tst_DragHandler::mouseDrag() |
| 231 | { |
| 232 | QFETCH(Qt::MouseButtons, acceptedButtons); |
| 233 | QFETCH(Qt::MouseButtons, dragButton); |
| 234 | bool shouldDrag = bool(acceptedButtons & dragButton); |
| 235 | |
| 236 | const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); |
| 237 | QScopedPointer<QQuickView> windowPtr; |
| 238 | createView(window&: windowPtr, fileName: "draggables.qml" ); |
| 239 | QQuickView * window = windowPtr.data(); |
| 240 | |
| 241 | QQuickItem *ball = window->rootObject()->childItems().first(); |
| 242 | QVERIFY(ball); |
| 243 | QQuickDragHandler *dragHandler = ball->findChild<QQuickDragHandler*>(); |
| 244 | QVERIFY(dragHandler); |
| 245 | dragHandler->setAcceptedButtons(acceptedButtons); // QTBUG-76875 |
| 246 | |
| 247 | QSignalSpy translationChangedSpy(dragHandler, SIGNAL(translationChanged())); |
| 248 | QSignalSpy centroidChangedSpy(dragHandler, SIGNAL(centroidChanged())); |
| 249 | |
| 250 | QPointF ballCenter = ball->clipRect().center(); |
| 251 | QPointF scenePressPos = ball->mapToScene(point: ballCenter); |
| 252 | QPoint p1 = scenePressPos.toPoint(); |
| 253 | QTest::mousePress(window, button: static_cast<Qt::MouseButton>(int(dragButton)), stateKey: Qt::NoModifier, pos: p1, delay: 500); |
| 254 | QVERIFY(!dragHandler->active()); |
| 255 | #if QT_CONFIG(cursor) |
| 256 | QCOMPARE(window->cursor().shape(), Qt::ArrowCursor); |
| 257 | #endif |
| 258 | if (shouldDrag) { |
| 259 | QCOMPARE(dragHandler->centroid().position(), ballCenter); |
| 260 | QCOMPARE(dragHandler->centroid().pressPosition(), ballCenter); |
| 261 | QCOMPARE(dragHandler->centroid().scenePosition(), scenePressPos); |
| 262 | QCOMPARE(dragHandler->centroid().scenePressPosition(), scenePressPos); |
| 263 | QCOMPARE(dragHandler->centroid().velocity(), QVector2D()); |
| 264 | QCOMPARE(centroidChangedSpy.count(), 1); |
| 265 | } |
| 266 | p1 += QPoint(dragThreshold, 0); |
| 267 | QTest::mouseMove(window, pos: p1); |
| 268 | if (shouldDrag) { |
| 269 | QTRY_VERIFY(dragHandler->centroid().velocity().x() > 0); |
| 270 | QCOMPARE(centroidChangedSpy.count(), 2); |
| 271 | QVERIFY(!dragHandler->active()); |
| 272 | #if QT_CONFIG(cursor) |
| 273 | QCOMPARE(window->cursor().shape(), Qt::ArrowCursor); |
| 274 | #endif |
| 275 | } |
| 276 | p1 += QPoint(1, 0); |
| 277 | QTest::mouseMove(window, pos: p1); |
| 278 | if (shouldDrag) |
| 279 | QTRY_VERIFY(dragHandler->active()); |
| 280 | else |
| 281 | QVERIFY(!dragHandler->active()); |
| 282 | QCOMPARE(translationChangedSpy.count(), 0); |
| 283 | if (shouldDrag) |
| 284 | QCOMPARE(centroidChangedSpy.count(), 3); |
| 285 | QCOMPARE(dragHandler->translation().x(), 0.0); |
| 286 | QPointF sceneGrabPos = p1; |
| 287 | if (shouldDrag) |
| 288 | QCOMPARE(dragHandler->centroid().sceneGrabPosition(), sceneGrabPos); |
| 289 | p1 += QPoint(19, 0); |
| 290 | QTest::mouseMove(window, pos: p1); |
| 291 | QVERIFY(shouldDrag ? dragHandler->active() : !dragHandler->active()); |
| 292 | if (shouldDrag) { |
| 293 | QCOMPARE(dragHandler->centroid().position(), ballCenter); |
| 294 | QCOMPARE(dragHandler->centroid().pressPosition(), ballCenter); |
| 295 | QCOMPARE(dragHandler->centroid().scenePosition(), ball->mapToScene(ballCenter)); |
| 296 | QCOMPARE(dragHandler->centroid().scenePressPosition(), scenePressPos); |
| 297 | QCOMPARE(dragHandler->centroid().sceneGrabPosition(), sceneGrabPos); |
| 298 | QCOMPARE(dragHandler->translation().x(), dragThreshold + 20.0); |
| 299 | QCOMPARE(dragHandler->translation().y(), 0.0); |
| 300 | QVERIFY(dragHandler->centroid().velocity().x() > 0); |
| 301 | QCOMPARE(centroidChangedSpy.count(), 4); |
| 302 | #if QT_CONFIG(cursor) |
| 303 | QCOMPARE(window->cursor().shape(), Qt::ClosedHandCursor); |
| 304 | #endif |
| 305 | } |
| 306 | QTest::mouseRelease(window, button: static_cast<Qt::MouseButton>(int(dragButton)), stateKey: Qt::NoModifier, pos: p1); |
| 307 | QTRY_VERIFY(!dragHandler->active()); |
| 308 | QCOMPARE(dragHandler->centroid().pressedButtons(), Qt::NoButton); |
| 309 | if (shouldDrag) |
| 310 | QCOMPARE(ball->mapToScene(ballCenter).toPoint(), p1); |
| 311 | QCOMPARE(translationChangedSpy.count(), shouldDrag ? 1 : 0); |
| 312 | QCOMPARE(centroidChangedSpy.count(), shouldDrag ? 5 : 0); |
| 313 | #if QT_CONFIG(cursor) |
| 314 | QTest::mouseMove(window, pos: p1 + QPoint(1, 0)); // TODO after fixing QTBUG-53987, don't send mouseMove |
| 315 | QCOMPARE(window->cursor().shape(), Qt::ArrowCursor); |
| 316 | #endif |
| 317 | } |
| 318 | |
| 319 | void tst_DragHandler::mouseDragThreshold_data() |
| 320 | { |
| 321 | QTest::addColumn<int>(name: "dragThreshold" ); |
| 322 | QTest::newRow(dataTag: "threshold zero" ) << 0; |
| 323 | QTest::newRow(dataTag: "threshold one" ) << 1; |
| 324 | QTest::newRow(dataTag: "threshold 20" ) << 20; |
| 325 | QTest::newRow(dataTag: "threshold default" ) << -1; |
| 326 | } |
| 327 | |
| 328 | void tst_DragHandler::mouseDragThreshold() |
| 329 | { |
| 330 | QFETCH(int, dragThreshold); |
| 331 | QScopedPointer<QQuickView> windowPtr; |
| 332 | createView(window&: windowPtr, fileName: "draggables.qml" ); |
| 333 | QQuickView * window = windowPtr.data(); |
| 334 | |
| 335 | QQuickItem *ball = window->rootObject()->childItems().first(); |
| 336 | QVERIFY(ball); |
| 337 | QQuickDragHandler *dragHandler = ball->findChild<QQuickDragHandler*>(); |
| 338 | QVERIFY(dragHandler); |
| 339 | if (dragThreshold < 0) { |
| 340 | dragThreshold = QGuiApplication::styleHints()->startDragDistance(); |
| 341 | QCOMPARE(dragHandler->dragThreshold(), dragThreshold); |
| 342 | } else { |
| 343 | dragHandler->setDragThreshold(dragThreshold); |
| 344 | } |
| 345 | |
| 346 | QSignalSpy translationChangedSpy(dragHandler, SIGNAL(translationChanged())); |
| 347 | QSignalSpy centroidChangedSpy(dragHandler, SIGNAL(centroidChanged())); |
| 348 | |
| 349 | QPointF ballCenter = ball->clipRect().center(); |
| 350 | QPointF scenePressPos = ball->mapToScene(point: ballCenter); |
| 351 | QPoint p1 = scenePressPos.toPoint(); |
| 352 | QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1); |
| 353 | QVERIFY(!dragHandler->active()); |
| 354 | QCOMPARE(dragHandler->centroid().position(), ballCenter); |
| 355 | QCOMPARE(dragHandler->centroid().pressPosition(), ballCenter); |
| 356 | QCOMPARE(dragHandler->centroid().scenePosition(), scenePressPos); |
| 357 | QCOMPARE(dragHandler->centroid().scenePressPosition(), scenePressPos); |
| 358 | QCOMPARE(dragHandler->centroid().velocity(), QVector2D()); |
| 359 | QCOMPARE(centroidChangedSpy.count(), 1); |
| 360 | p1 += QPoint(qMax(a: 1, b: dragThreshold), 0); // QTBUG-85431: zero-distance mouse moves are not delivered |
| 361 | QTest::mouseMove(window, pos: p1); |
| 362 | if (dragThreshold > 0) |
| 363 | QTRY_VERIFY(dragHandler->centroid().velocity().x() > 0); |
| 364 | QCOMPARE(centroidChangedSpy.count(), 2); |
| 365 | // the handler is not yet active, unless the drag threshold was already exceeded |
| 366 | QCOMPARE(dragHandler->active(), dragThreshold == 0); |
| 367 | p1 += QPoint(1, 0); |
| 368 | QTest::mouseMove(window, pos: p1); |
| 369 | QTRY_VERIFY(dragHandler->active()); |
| 370 | QCOMPARE(translationChangedSpy.count(), dragThreshold ? 0 : 1); |
| 371 | QCOMPARE(centroidChangedSpy.count(), 3); |
| 372 | QCOMPARE(dragHandler->translation().x(), dragThreshold ? 0 : 2); |
| 373 | QPointF sceneGrabPos = dragThreshold ? p1 : p1 - QPoint(1, 0); |
| 374 | QCOMPARE(dragHandler->centroid().sceneGrabPosition(), sceneGrabPos); |
| 375 | p1 += QPoint(19, 0); |
| 376 | QTest::mouseMove(window, pos: p1); |
| 377 | QTRY_VERIFY(dragHandler->active()); |
| 378 | QCOMPARE(dragHandler->centroid().position(), ballCenter); |
| 379 | QCOMPARE(dragHandler->centroid().pressPosition(), ballCenter); |
| 380 | QCOMPARE(dragHandler->centroid().scenePosition(), ball->mapToScene(ballCenter)); |
| 381 | QCOMPARE(dragHandler->centroid().scenePressPosition(), scenePressPos); |
| 382 | QCOMPARE(dragHandler->centroid().sceneGrabPosition(), sceneGrabPos); |
| 383 | QCOMPARE(dragHandler->translation().x(), dragThreshold + (dragThreshold ? 20 : 21)); |
| 384 | QCOMPARE(dragHandler->translation().y(), 0.0); |
| 385 | QVERIFY(dragHandler->centroid().velocity().x() > 0); |
| 386 | QCOMPARE(centroidChangedSpy.count(), 4); |
| 387 | QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1); |
| 388 | QTRY_VERIFY(!dragHandler->active()); |
| 389 | QCOMPARE(dragHandler->centroid().pressedButtons(), Qt::NoButton); |
| 390 | QCOMPARE(ball->mapToScene(ballCenter).toPoint(), p1); |
| 391 | QCOMPARE(translationChangedSpy.count(), dragThreshold ? 1 : 2); |
| 392 | QCOMPARE(centroidChangedSpy.count(), 5); |
| 393 | } |
| 394 | |
| 395 | void tst_DragHandler::dragFromMargin() // QTBUG-74966 |
| 396 | { |
| 397 | const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); |
| 398 | QScopedPointer<QQuickView> windowPtr; |
| 399 | createView(window&: windowPtr, fileName: "dragMargin.qml" ); |
| 400 | QQuickView * window = windowPtr.data(); |
| 401 | |
| 402 | QQuickItem *draggableItem = window->rootObject()->childItems().first(); |
| 403 | QVERIFY(draggableItem); |
| 404 | QQuickDragHandler *dragHandler = draggableItem->findChild<QQuickDragHandler*>(); |
| 405 | QVERIFY(dragHandler); |
| 406 | |
| 407 | QPointF originalPos = draggableItem->position(); |
| 408 | QPointF scenePressPos = originalPos - QPointF(10, 0); |
| 409 | QPoint p1 = scenePressPos.toPoint(); |
| 410 | QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1); |
| 411 | QVERIFY(!dragHandler->active()); |
| 412 | QCOMPARE(dragHandler->centroid().scenePosition(), scenePressPos); |
| 413 | QCOMPARE(dragHandler->centroid().scenePressPosition(), scenePressPos); |
| 414 | #if QT_CONFIG(cursor) |
| 415 | QCOMPARE(window->cursor().shape(), Qt::ArrowCursor); |
| 416 | #endif |
| 417 | p1 += QPoint(dragThreshold * 2, 0); |
| 418 | QTest::mouseMove(window, pos: p1); |
| 419 | QTRY_VERIFY(dragHandler->active()); |
| 420 | QCOMPARE(dragHandler->centroid().scenePressPosition(), scenePressPos); |
| 421 | QCOMPARE(dragHandler->centroid().sceneGrabPosition(), p1); |
| 422 | QCOMPARE(dragHandler->translation().x(), 0.0); // hmm that's odd |
| 423 | QCOMPARE(dragHandler->translation().y(), 0.0); |
| 424 | QCOMPARE(draggableItem->position(), originalPos + QPointF(dragThreshold * 2, 0)); |
| 425 | #if QT_CONFIG(cursor) |
| 426 | // The cursor doesn't change until the next event after the handler becomes active. |
| 427 | p1 += QPoint(1, 0); |
| 428 | QTest::mouseMove(window, pos: p1); |
| 429 | QTRY_COMPARE(window->cursor().shape(), Qt::ClosedHandCursor); |
| 430 | #endif |
| 431 | QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1); |
| 432 | QTRY_VERIFY(!dragHandler->active()); |
| 433 | QCOMPARE(dragHandler->centroid().pressedButtons(), Qt::NoButton); |
| 434 | #if QT_CONFIG(cursor) |
| 435 | QTRY_COMPARE(window->cursor().shape(), Qt::ArrowCursor); |
| 436 | #endif |
| 437 | } |
| 438 | |
| 439 | void tst_DragHandler::snapMode_data() |
| 440 | { |
| 441 | const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); |
| 442 | QTest::addColumn<QString>(name: "subTree" ); |
| 443 | QTest::addColumn<int>(name: "snapMode" ); |
| 444 | QTest::addColumn<QPoint>(name: "startDragPos" ); |
| 445 | QTest::addColumn<QPoint>(name: "dragMovement" ); |
| 446 | QTest::addColumn<QPoint>(name: "expectedMovement" ); |
| 447 | |
| 448 | struct TestEntry { |
| 449 | const char *desc; |
| 450 | const char *subTree; |
| 451 | QQuickDragHandler::SnapMode mode; |
| 452 | QPoint startDragPos; |
| 453 | QPoint dragMovement; |
| 454 | QPoint expectedMovement; |
| 455 | }; |
| 456 | |
| 457 | TestEntry testdata[] = { |
| 458 | {.desc: "outside the target" , .subTree: "rect1" , .mode: QQuickDragHandler::SnapAuto, .startDragPos: QPoint(45, -10), .dragMovement: QPoint(dragThreshold*2, 0), .expectedMovement: QPoint(dragThreshold*2, 0)}, |
| 459 | {.desc: "inside the target" , .subTree: "rect1" , .mode: QQuickDragHandler::SnapAuto, .startDragPos: QPoint(45, 10), .dragMovement: QPoint(dragThreshold*2, 0), .expectedMovement: QPoint(dragThreshold*2, 0)}, |
| 460 | {.desc: "outside the target" , .subTree: "rect1" , .mode: QQuickDragHandler::SnapAlways, .startDragPos: QPoint(45, -10), .dragMovement: QPoint(dragThreshold*2, 0), .expectedMovement: QPoint(dragThreshold*2, -50-10)}, |
| 461 | {.desc: "outside the target" , .subTree: "rect1" , .mode: QQuickDragHandler::NoSnap, .startDragPos: QPoint(45, -10), .dragMovement: QPoint(dragThreshold*2, 0), .expectedMovement: QPoint(dragThreshold*2, 0)}, |
| 462 | {.desc: "outside the target" , .subTree: "rect1" , .mode: QQuickDragHandler::SnapIfPressedOutsideTarget, .startDragPos: QPoint(45, -10), .dragMovement: QPoint(dragThreshold*2, 0), .expectedMovement: QPoint(dragThreshold*2, -50-10)}, |
| 463 | {.desc: "inside the target" , .subTree: "rect1" , .mode: QQuickDragHandler::SnapIfPressedOutsideTarget, .startDragPos: QPoint(45, 10), .dragMovement: QPoint(dragThreshold*2, 0), .expectedMovement: QPoint(dragThreshold*2, 0)}, |
| 464 | //targets y pos moves from -25 to (25 + dragThreshold*2) because of snapping to center: |
| 465 | {.desc: "outside target, should snap" , .subTree: "rect2" , .mode: QQuickDragHandler::SnapAuto, .startDragPos: QPoint(45, 50), .dragMovement: QPoint(0, dragThreshold*2), .expectedMovement: QPoint(0, 25 + 25 + dragThreshold*2)}, |
| 466 | {.desc: "inside target, shouldn't snap" , .subTree: "rect2" , .mode: QQuickDragHandler::SnapAuto, .startDragPos: QPoint(45, 10), .dragMovement: QPoint(0, dragThreshold*2), .expectedMovement: QPoint(0, dragThreshold*2)} |
| 467 | }; |
| 468 | |
| 469 | for (const TestEntry& e : testdata) { |
| 470 | const QMetaEnum = QMetaEnum::fromType<QQuickDragHandler::SnapMode>(); |
| 471 | const QString dataTag = QString::fromLatin1(str: "%1, %2, %3" ).arg(a: e.subTree).arg(a: menum.valueToKey(value: e.mode)).arg(a: e.desc); |
| 472 | QTest::newRow(dataTag: dataTag.toUtf8().constData()) << e.subTree << (int)e.mode |
| 473 | << e.startDragPos << e.dragMovement << e.expectedMovement; |
| 474 | } |
| 475 | } |
| 476 | |
| 477 | void tst_DragHandler::snapMode() |
| 478 | { |
| 479 | QFETCH(QString, subTree); |
| 480 | QFETCH(QPoint, startDragPos); |
| 481 | QFETCH(QPoint, dragMovement); |
| 482 | QFETCH(int, snapMode); |
| 483 | QFETCH(QPoint, expectedMovement); |
| 484 | |
| 485 | QScopedPointer<QQuickView> windowPtr; |
| 486 | createView(window&: windowPtr, fileName: "snapMode.qml" ); |
| 487 | QQuickView * window = windowPtr.data(); |
| 488 | |
| 489 | QQuickItem *rect1 = window->rootObject()->findChild<QQuickItem*>(aName: subTree); |
| 490 | QVERIFY(rect1); |
| 491 | QQuickItem *rect1b = rect1->childItems().first(); |
| 492 | QVERIFY(rect1b); |
| 493 | QQuickDragHandler *dragHandler1 = rect1->findChild<QQuickDragHandler*>(); |
| 494 | QVERIFY(dragHandler1); |
| 495 | dragHandler1->setSnapMode((QQuickDragHandler::SnapMode)snapMode); |
| 496 | QQuickItem *dragTarget = dragHandler1->target(); |
| 497 | QPointF oldTargetPos = dragTarget->position(); |
| 498 | |
| 499 | QPoint p1 = rect1->mapToScene(point: QPointF(startDragPos)).toPoint(); |
| 500 | QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1); |
| 501 | QVERIFY(!dragHandler1->active()); |
| 502 | p1 += dragMovement; |
| 503 | QTest::mouseMove(window, pos: p1); |
| 504 | QTRY_VERIFY(dragHandler1->active()); |
| 505 | QCOMPARE(dragTarget->position(), oldTargetPos + expectedMovement); |
| 506 | QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1); |
| 507 | QTRY_VERIFY(!dragHandler1->active()); |
| 508 | QCOMPARE(dragHandler1->centroid().pressedButtons(), Qt::NoButton); |
| 509 | } |
| 510 | |
| 511 | void tst_DragHandler::touchDragMulti() |
| 512 | { |
| 513 | const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); |
| 514 | QScopedPointer<QQuickView> windowPtr; |
| 515 | createView(window&: windowPtr, fileName: "draggables.qml" ); |
| 516 | QQuickView * window = windowPtr.data(); |
| 517 | |
| 518 | QQuickItem *ball1 = window->rootObject()->childItems().first(); |
| 519 | QVERIFY(ball1); |
| 520 | QQuickDragHandler *dragHandler1 = ball1->findChild<QQuickDragHandler*>(); |
| 521 | QVERIFY(dragHandler1); |
| 522 | QSignalSpy translationChangedSpy1(dragHandler1, SIGNAL(translationChanged())); |
| 523 | QSignalSpy centroidChangedSpy1(dragHandler1, SIGNAL(centroidChanged())); |
| 524 | |
| 525 | QQuickItem *ball2 = window->rootObject()->childItems().at(i: 1); |
| 526 | QVERIFY(ball2); |
| 527 | QQuickDragHandler *dragHandler2 = ball2->findChild<QQuickDragHandler*>(); |
| 528 | QVERIFY(dragHandler2); |
| 529 | QSignalSpy translationChangedSpy2(dragHandler2, SIGNAL(translationChanged())); |
| 530 | QSignalSpy centroidChangedSpy2(dragHandler1, SIGNAL(centroidChanged())); |
| 531 | |
| 532 | QPointF ball1Center = ball1->clipRect().center(); |
| 533 | QPointF scenePressPos1 = ball1->mapToScene(point: ball1Center); |
| 534 | QPoint p1 = scenePressPos1.toPoint(); |
| 535 | QPointF ball2Center = ball2->clipRect().center(); |
| 536 | QPointF scenePressPos2 = ball2->mapToScene(point: ball2Center); |
| 537 | QPoint p2 = scenePressPos2.toPoint(); |
| 538 | QTest::QTouchEventSequence touchSeq = QTest::touchEvent(window, device: touchDevice, autoCommit: false); |
| 539 | |
| 540 | touchSeq.press(touchId: 1, pt: p1, window).press(touchId: 2, pt: p2, window).commit(); |
| 541 | QQuickTouchUtils::flush(window); |
| 542 | QVERIFY(!dragHandler1->active()); |
| 543 | QCOMPARE(centroidChangedSpy1.count(), 1); |
| 544 | QCOMPARE(dragHandler1->centroid().position(), ball1Center); |
| 545 | QCOMPARE(dragHandler1->centroid().pressPosition(), ball1Center); |
| 546 | QCOMPARE(dragHandler1->centroid().scenePosition(), scenePressPos1); |
| 547 | QCOMPARE(dragHandler1->centroid().scenePressPosition(), scenePressPos1); |
| 548 | QVERIFY(!dragHandler2->active()); |
| 549 | QCOMPARE(centroidChangedSpy2.count(), 1); |
| 550 | QCOMPARE(dragHandler2->centroid().position(), ball2Center); |
| 551 | QCOMPARE(dragHandler2->centroid().pressPosition(), ball2Center); |
| 552 | QCOMPARE(dragHandler2->centroid().scenePosition(), scenePressPos2); |
| 553 | QCOMPARE(dragHandler2->centroid().scenePressPosition(), scenePressPos2); |
| 554 | p1 += QPoint(dragThreshold, 0); |
| 555 | p2 += QPoint(0, dragThreshold); |
| 556 | touchSeq.move(touchId: 1, pt: p1, window).move(touchId: 2, pt: p2, window).commit(); |
| 557 | QQuickTouchUtils::flush(window); |
| 558 | QVERIFY(!dragHandler1->active()); |
| 559 | QCOMPARE(centroidChangedSpy1.count(), 2); |
| 560 | QCOMPARE(dragHandler1->centroid().position(), ball1Center + QPointF(dragThreshold, 0)); |
| 561 | QCOMPARE(dragHandler1->centroid().pressPosition(), ball1Center); |
| 562 | QCOMPARE(dragHandler1->centroid().scenePosition().toPoint(), p1); |
| 563 | QCOMPARE(dragHandler1->centroid().scenePressPosition(), scenePressPos1); |
| 564 | QVERIFY(!dragHandler2->active()); |
| 565 | QCOMPARE(centroidChangedSpy2.count(), 2); |
| 566 | QCOMPARE(dragHandler2->centroid().position(), ball2Center + QPointF(0, dragThreshold)); |
| 567 | QCOMPARE(dragHandler2->centroid().pressPosition(), ball2Center); |
| 568 | QCOMPARE(dragHandler2->centroid().scenePosition().toPoint(), p2); |
| 569 | QCOMPARE(dragHandler2->centroid().scenePressPosition(), scenePressPos2); |
| 570 | p1 += QPoint(1, 0); |
| 571 | p2 += QPoint(0, 1); |
| 572 | touchSeq.move(touchId: 1, pt: p1, window).move(touchId: 2, pt: p2, window).commit(); |
| 573 | QQuickTouchUtils::flush(window); |
| 574 | QTRY_VERIFY(dragHandler1->active()); |
| 575 | QVERIFY(dragHandler2->active()); |
| 576 | QCOMPARE(translationChangedSpy1.count(), 0); |
| 577 | QCOMPARE(dragHandler1->translation().x(), 0.0); |
| 578 | QPointF sceneGrabPos1 = p1; |
| 579 | QPointF sceneGrabPos2 = p2; |
| 580 | QCOMPARE(dragHandler1->centroid().sceneGrabPosition(), sceneGrabPos1); |
| 581 | QCOMPARE(dragHandler2->centroid().sceneGrabPosition(), sceneGrabPos2); |
| 582 | p1 += QPoint(19, 0); |
| 583 | p2 += QPoint(0, 19); |
| 584 | QVERIFY(dragHandler2->active()); |
| 585 | QCOMPARE(translationChangedSpy2.count(), 0); |
| 586 | QCOMPARE(dragHandler2->translation().x(), 0.0); |
| 587 | QCOMPARE(dragHandler2->centroid().sceneGrabPosition(), sceneGrabPos2); |
| 588 | touchSeq.move(touchId: 1, pt: p1, window).move(touchId: 2, pt: p2, window).commit(); |
| 589 | QQuickTouchUtils::flush(window); |
| 590 | QVERIFY(dragHandler1->active()); |
| 591 | QVERIFY(dragHandler2->active()); |
| 592 | QCOMPARE(dragHandler1->centroid().position(), ball1Center); |
| 593 | QCOMPARE(dragHandler1->centroid().pressPosition(), ball1Center); |
| 594 | QCOMPARE(dragHandler1->centroid().scenePosition(), ball1->mapToScene(ball1Center)); |
| 595 | QCOMPARE(dragHandler1->centroid().scenePressPosition(), scenePressPos1); |
| 596 | QCOMPARE(dragHandler1->centroid().sceneGrabPosition(), sceneGrabPos1); |
| 597 | QCOMPARE(dragHandler1->translation().x(), dragThreshold + 20.0); |
| 598 | QCOMPARE(dragHandler1->translation().y(), 0.0); |
| 599 | QCOMPARE(dragHandler2->centroid().position(), ball2Center); |
| 600 | QCOMPARE(dragHandler2->centroid().pressPosition(), ball2Center); |
| 601 | QCOMPARE(dragHandler2->centroid().scenePosition(), ball2->mapToScene(ball2Center)); |
| 602 | QCOMPARE(dragHandler2->centroid().scenePressPosition(), scenePressPos2); |
| 603 | QCOMPARE(dragHandler2->centroid().sceneGrabPosition(), sceneGrabPos2); |
| 604 | QCOMPARE(dragHandler2->translation().x(), 0.0); |
| 605 | QCOMPARE(dragHandler2->translation().y(), dragThreshold + 20.0); |
| 606 | touchSeq.release(touchId: 1, pt: p1, window).stationary(touchId: 2).commit(); |
| 607 | QQuickTouchUtils::flush(window); |
| 608 | QTRY_VERIFY(!dragHandler1->active()); |
| 609 | QVERIFY(dragHandler2->active()); |
| 610 | QCOMPARE(dragHandler1->centroid().pressedButtons(), Qt::NoButton); |
| 611 | QCOMPARE(ball1->mapToScene(ball1Center).toPoint(), p1); |
| 612 | QCOMPARE(translationChangedSpy1.count(), 1); |
| 613 | touchSeq.release(touchId: 2, pt: p2, window).commit(); |
| 614 | QQuickTouchUtils::flush(window); |
| 615 | QTRY_VERIFY(!dragHandler2->active()); |
| 616 | QCOMPARE(ball2->mapToScene(ball2Center).toPoint(), p2); |
| 617 | QCOMPARE(translationChangedSpy2.count(), 1); |
| 618 | } |
| 619 | |
| 620 | void tst_DragHandler::touchDragMultiSliders_data() |
| 621 | { |
| 622 | QTest::addColumn<int>(name: "sliderRow" ); |
| 623 | QTest::addColumn<QVector<int> >(name: "whichSliders" ); |
| 624 | QTest::addColumn<QVector<int> >(name: "startingCenterOffsets" ); |
| 625 | QTest::addColumn<QVector<QVector2D> >(name: "movements" ); |
| 626 | |
| 627 | QTest::newRow(dataTag: "Drag Knob: start on the knobs, drag down" ) << |
| 628 | 0 << QVector<int> { 0, 1, 2 } << QVector<int> { 0, 0, 0 } << QVector<QVector2D> { {0, 60}, {0, 60}, {0, 60} }; |
| 629 | QTest::newRow(dataTag: "Drag Knob: start on the knobs, drag diagonally downward" ) << |
| 630 | 0 << QVector<int> { 0, 1, 2 } << QVector<int> { 0, 0, 0 } << QVector<QVector2D> { {20, 40}, {20, 60}, {20, 80} }; |
| 631 | QTest::newRow(dataTag: "Drag Anywhere: start on the knobs, drag down" ) << |
| 632 | 1 << QVector<int> { 0, 1, 2 } << QVector<int> { 0, 0, 0 } << QVector<QVector2D> { {0, 60}, {0, 60}, {0, 60} }; |
| 633 | QTest::newRow(dataTag: "Drag Anywhere: start on the knobs, drag diagonally downward" ) << |
| 634 | 1 << QVector<int> { 0, 1, 2 } << QVector<int> { 0, 0, 0 } << QVector<QVector2D> { {20, 40}, {20, 60}, {20, 80} }; |
| 635 | // TODO these next two fail because the DragHandler grabs when a finger |
| 636 | // drags across it from outside, but should rather start only if it is pressed inside |
| 637 | // QTest::newRow("Drag Knob: start above the knobs, drag down") << |
| 638 | // 0 << QVector<int> { 0, 1, 2 } << QVector<int> { -30, -30, -30 } << QVector<QVector2D> { {0, 40}, {0, 60}, {0, 80} }; |
| 639 | // QTest::newRow("Drag Knob: start above the knobs, drag diagonally downward") << |
| 640 | // 0 << QVector<int> { 0, 1, 2 } << QVector<int> { -30, -30, -30 } << QVector<QVector2D> { {20, 40}, {20, 60}, {20, 80} }; |
| 641 | QTest::newRow(dataTag: "Drag Anywhere: start above the knobs, drag down" ) << |
| 642 | 1 << QVector<int> { 0, 1, 2 } << QVector<int> { -20, -30, -40 } << QVector<QVector2D> { {0, 60}, {0, 60}, {0, 60} }; |
| 643 | QTest::newRow(dataTag: "Drag Anywhere: start above the knobs, drag diagonally downward" ) << |
| 644 | 1 << QVector<int> { 0, 1, 2 } << QVector<int> { -20, -30, -40 } << QVector<QVector2D> { {20, 40}, {20, 60}, {20, 80} }; |
| 645 | } |
| 646 | |
| 647 | void tst_DragHandler::touchDragMultiSliders() |
| 648 | { |
| 649 | QFETCH(int, sliderRow); |
| 650 | QFETCH(QVector<int>, whichSliders); |
| 651 | QFETCH(QVector<int>, startingCenterOffsets); |
| 652 | QFETCH(QVector<QVector2D>, movements); |
| 653 | const int moveCount = 8; |
| 654 | |
| 655 | QScopedPointer<QQuickView> windowPtr; |
| 656 | createView(window&: windowPtr, fileName: "multipleSliders.qml" ); |
| 657 | QQuickView * window = windowPtr.data(); |
| 658 | QTest::QTouchEventSequence touch = QTest::touchEvent(window, device: touchDevice); |
| 659 | |
| 660 | QQuickRepeater *rowRepeater = window->rootObject()->findChildren<QQuickRepeater *>()[sliderRow]; |
| 661 | QVector<QQuickItem *> knobs; |
| 662 | QVector<QQuickDragHandler *> dragHandlers; |
| 663 | QVector<QQuickTapHandler *> tapHandlers; |
| 664 | QVector<QPointF> startPoints; |
| 665 | for (int sli : whichSliders) { |
| 666 | QQuickItem *slider = rowRepeater->itemAt(index: sli); |
| 667 | QVERIFY(slider); |
| 668 | dragHandlers << slider->findChild<QQuickDragHandler*>(); |
| 669 | QVERIFY(dragHandlers[sli]); |
| 670 | tapHandlers << slider->findChild<QQuickTapHandler*>(); |
| 671 | QVERIFY(tapHandlers[sli]); |
| 672 | knobs << tapHandlers[sli]->parentItem(); |
| 673 | QPointF startPoint = knobs[sli]->mapToScene(point: knobs[sli]->clipRect().center()); |
| 674 | startPoint.setY(startPoint.y() + startingCenterOffsets[sli]); |
| 675 | startPoints << startPoint; |
| 676 | qCDebug(lcPointerTests) << "row" << sliderRow << "slider" << sli << slider->objectName() << |
| 677 | "start" << startingCenterOffsets[sli] << startPoints[sli]; |
| 678 | } |
| 679 | QVector<QPointF> touchPoints = startPoints; |
| 680 | |
| 681 | // Press |
| 682 | for (int sli : whichSliders) |
| 683 | touch.press(touchId: sli, pt: touchPoints[sli].toPoint()); |
| 684 | touch.commit(); |
| 685 | |
| 686 | // Moves |
| 687 | for (int m = 0; m < moveCount; ++m) { |
| 688 | for (int sli : whichSliders) { |
| 689 | QVector2D incr = movements[sli] / moveCount; |
| 690 | touchPoints[sli] += incr.toPointF(); |
| 691 | touch.move(touchId: sli, pt: touchPoints[sli].toPoint()); |
| 692 | } |
| 693 | touch.commit(); |
| 694 | QQuickTouchUtils::flush(window); |
| 695 | } |
| 696 | |
| 697 | // Check that they moved to where they should: since the slider is constrained, |
| 698 | // only the y component should have an effect; knobs should not come out of their "grooves" |
| 699 | for (int sli : whichSliders) { |
| 700 | QPoint endPosition = knobs[sli]->mapToScene(point: knobs[sli]->clipRect().center()).toPoint(); |
| 701 | QPoint expectedEndPosition(startPoints[sli].x(), startPoints[sli].y() + movements[sli].y()); |
| 702 | if (sliderRow == 0 && qAbs(t: startingCenterOffsets[sli]) > knobs[sli]->height() / 2) |
| 703 | expectedEndPosition = startPoints[sli].toPoint(); |
| 704 | qCDebug(lcPointerTests) << "slider " << knobs[sli]->objectName() << "started @" << startPoints[sli] |
| 705 | << "tried to move by" << movements[sli] << "ended up @" << endPosition << "expected" << expectedEndPosition; |
| 706 | QTRY_COMPARE(endPosition, expectedEndPosition); |
| 707 | } |
| 708 | |
| 709 | // Release |
| 710 | for (int sli : whichSliders) |
| 711 | touch.release(touchId: sli, pt: touchPoints[sli].toPoint()); |
| 712 | touch.commit(); |
| 713 | } |
| 714 | |
| 715 | void tst_DragHandler::touchPassiveGrabbers_data() |
| 716 | { |
| 717 | QTest::addColumn<QString>(name: "itemName" ); |
| 718 | QTest::addColumn<QStringList>(name: "expectedPassiveGrabberNames" ); |
| 719 | |
| 720 | QTest::newRow(dataTag: "Drag And Tap" ) << "dragAndTap" << QStringList({"drag" , "tap" }); |
| 721 | QTest::newRow(dataTag: "Tap And Drag" ) << "tapAndDrag" << QStringList({"tap" , "drag" }); |
| 722 | QTest::newRow(dataTag: "Drag And Tap (not siblings)" ) << "dragAndTapNotSiblings" << QStringList({"drag" , "tap" }); |
| 723 | QTest::newRow(dataTag: "Tap And Drag (not siblings)" ) << "tapAndDragNotSiblings" << QStringList({"tap" , "drag" }); |
| 724 | } |
| 725 | |
| 726 | void tst_DragHandler::touchPassiveGrabbers() |
| 727 | { |
| 728 | QFETCH(QString, itemName); |
| 729 | QFETCH(QStringList, expectedPassiveGrabberNames); |
| 730 | |
| 731 | QScopedPointer<QQuickView> windowPtr; |
| 732 | createView(window&: windowPtr, fileName: "simpleTapAndDragHandlers.qml" ); |
| 733 | QQuickView * window = windowPtr.data(); |
| 734 | |
| 735 | QQuickItem *row2 = window->rootObject()->findChild<QQuickItem*>(aName: itemName); |
| 736 | QSet<QQuickPointerHandler *> expectedPassiveGrabbers; |
| 737 | for (QString objectName : expectedPassiveGrabberNames) |
| 738 | expectedPassiveGrabbers << row2->findChild<QQuickPointerHandler*>(aName: objectName); |
| 739 | |
| 740 | QPointF p1 = row2->mapToScene(point: row2->clipRect().center()); |
| 741 | QTest::QTouchEventSequence touch = QTest::touchEvent(window, device: touchDevice); |
| 742 | touch.press(touchId: 1, pt: p1.toPoint()).commit(); |
| 743 | QQuickTouchUtils::flush(window); |
| 744 | |
| 745 | QCOMPARE(passiveGrabbers(window), expectedPassiveGrabbers); |
| 746 | |
| 747 | QQuickDragHandler *dragHandler = nullptr; |
| 748 | for (QQuickPointerHandler *handler: expectedPassiveGrabbers) { |
| 749 | QPointF scenePressPos; |
| 750 | if (QQuickMultiPointHandler *mph = qmlobject_cast<QQuickMultiPointHandler *>(object: handler)) |
| 751 | scenePressPos = mph->centroid().scenePressPosition(); |
| 752 | else |
| 753 | scenePressPos = static_cast<QQuickSinglePointHandler *>(handler)->point().scenePressPosition(); |
| 754 | QCOMPARE(scenePressPos, p1); |
| 755 | QQuickDragHandler *dh = qmlobject_cast<QQuickDragHandler *>(object: handler); |
| 756 | if (dh) |
| 757 | dragHandler = dh; |
| 758 | } |
| 759 | QVERIFY(dragHandler); |
| 760 | QPointF initialPos = dragHandler->target()->position(); |
| 761 | |
| 762 | p1 += QPointF(50, 50); |
| 763 | touch.move(touchId: 1, pt: p1.toPoint()).commit(); |
| 764 | QQuickTouchUtils::flush(window); |
| 765 | QTRY_VERIFY(dragHandler->active()); |
| 766 | |
| 767 | p1 += QPointF(50, 50); |
| 768 | touch.move(touchId: 1, pt: p1.toPoint()).commit(); |
| 769 | QQuickTouchUtils::flush(window); |
| 770 | QPointF movementDelta = dragHandler->target()->position() - initialPos; |
| 771 | qCDebug(lcPointerTests) << "DragHandler moved the target by" << movementDelta; |
| 772 | QVERIFY(movementDelta.x() >= 100); |
| 773 | QVERIFY(movementDelta.y() >= 100); |
| 774 | |
| 775 | QTest::qWait(ms: 500); |
| 776 | |
| 777 | touch.release(touchId: 1, pt: p1.toPoint()); |
| 778 | touch.commit(); |
| 779 | QQuickTouchUtils::flush(window); |
| 780 | } |
| 781 | |
| 782 | void tst_DragHandler::touchPinchAndMouseMove() |
| 783 | { |
| 784 | QScopedPointer<QQuickView> windowPtr; |
| 785 | createView(window&: windowPtr, fileName: "draghandler_and_pinchhandler.qml" ); |
| 786 | QQuickView *window = windowPtr.data(); |
| 787 | QQuickItem *rect = window->rootObject()->findChild<QQuickItem*>(aName: QLatin1String("Rect" )); |
| 788 | QQuickPointerHandler *pinchHandler = window->rootObject()->findChild<QQuickPointerHandler*>(aName: QLatin1String("PinchHandler" )); |
| 789 | |
| 790 | QPoint p1(150,200); |
| 791 | QPoint p2(250,200); |
| 792 | |
| 793 | // Trigger a scale pinch, PinchHandler should activate |
| 794 | QTest::QTouchEventSequence touch = QTest::touchEvent(window, device: touchDevice); |
| 795 | touch.press(touchId: 1, pt: p1).press(touchId: 2, pt: p2).commit(); |
| 796 | QQuickTouchUtils::flush(window); |
| 797 | QPoint delta(10,0); |
| 798 | for (int i = 0; i < 10 && !pinchHandler->active(); ++i) { |
| 799 | p1-=delta; |
| 800 | p2+=delta; |
| 801 | touch.move(touchId: 1, pt: p1).move(touchId: 2, pt: p2).commit(); |
| 802 | QQuickTouchUtils::flush(window); |
| 803 | } |
| 804 | QCOMPARE(pinchHandler->active(), true); |
| 805 | |
| 806 | // While having the touch points pressed, send wrong mouse event as MS Windows did: |
| 807 | // * A MoveMove with LeftButton down |
| 808 | // (in order to synthesize that, qtestMouseButtons needs to be modified) |
| 809 | // (This will make the DragHandler do a passive grab) |
| 810 | QTestPrivate::qtestMouseButtons = Qt::LeftButton; |
| 811 | QTest::mouseMove(window, pos: p1 + delta); |
| 812 | |
| 813 | touch.release(touchId: 1, pt: p1).release(touchId: 2, pt: p2).commit(); |
| 814 | QQuickTouchUtils::flush(window); |
| 815 | |
| 816 | // Now move the mouse with no buttons down and check if the rect did not move |
| 817 | // At this point, no touch points are pressed and no mouse buttons are pressed. |
| 818 | QTestPrivate::qtestMouseButtons = Qt::NoButton; |
| 819 | QSignalSpy rectMovedSpy(rect, SIGNAL(xChanged())); |
| 820 | for (int i = 0; i < 10; ++i) { |
| 821 | p1 += delta; |
| 822 | QTest::mouseMove(window, pos: p1); |
| 823 | QCOMPARE(rectMovedSpy.count(), 0); |
| 824 | } |
| 825 | } |
| 826 | |
| 827 | class ModalLayer : public QQuickItem { |
| 828 | public: |
| 829 | explicit ModalLayer(QQuickItem* parent = nullptr) : QQuickItem(parent) { |
| 830 | this->setAcceptedMouseButtons(Qt::AllButtons); |
| 831 | this->setAcceptTouchEvents(true); |
| 832 | this->setKeepMouseGrab(true); |
| 833 | this->setKeepTouchGrab(true); |
| 834 | } |
| 835 | |
| 836 | bool event(QEvent* event) override { |
| 837 | switch (event->type()) { |
| 838 | case QEvent::KeyPress: |
| 839 | case QEvent::MouseMove: |
| 840 | case QEvent::MouseButtonPress: |
| 841 | case QEvent::MouseButtonRelease: |
| 842 | case QEvent::MouseTrackingChange: |
| 843 | case QEvent::MouseButtonDblClick: |
| 844 | case QEvent::Wheel: |
| 845 | case QEvent::TouchBegin: |
| 846 | case QEvent::TouchUpdate: |
| 847 | case QEvent::TouchCancel: |
| 848 | case QEvent::TouchEnd: { |
| 849 | qCDebug(lcPointerTests) << "BLOCK!" << event->type(); |
| 850 | return true; |
| 851 | } |
| 852 | default: break; |
| 853 | } |
| 854 | return QQuickItem::event(event); |
| 855 | } |
| 856 | }; |
| 857 | |
| 858 | void tst_DragHandler::underModalLayer() // QTBUG-78258 |
| 859 | { |
| 860 | qmlRegisterType<ModalLayer>(uri: "Test" , versionMajor: 1, versionMinor: 0, qmlName: "ModalLayer" ); |
| 861 | |
| 862 | const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); |
| 863 | QScopedPointer<QQuickView> windowPtr; |
| 864 | createView(window&: windowPtr, fileName: "dragHandlerUnderModalLayer.qml" ); |
| 865 | QQuickView * window = windowPtr.data(); |
| 866 | QPointer<QQuickDragHandler> dragHandler = window->rootObject()->findChild<QQuickDragHandler*>(); |
| 867 | QVERIFY(dragHandler); |
| 868 | |
| 869 | QPoint p1(250, 250); |
| 870 | QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1); |
| 871 | p1 += QPoint(dragThreshold, dragThreshold); |
| 872 | QTest::mouseMove(window, pos: p1); |
| 873 | QVERIFY(!dragHandler->active()); |
| 874 | p1 += QPoint(dragThreshold, dragThreshold); |
| 875 | QTest::mouseMove(window, pos: p1); |
| 876 | QVERIFY(!dragHandler->active()); |
| 877 | QTest::mouseRelease(window, button: Qt::LeftButton); |
| 878 | } |
| 879 | |
| 880 | QTEST_MAIN(tst_DragHandler) |
| 881 | |
| 882 | #include "tst_qquickdraghandler.moc" |
| 883 | |
| 884 | |